Step 521: refresh notification and status signaling semantics
This commit is contained in:
@@ -3568,4 +3568,13 @@ target_link_libraries(step520_test PRIVATE
|
||||
tree_sitter_javascript tree_sitter_typescript
|
||||
tree_sitter_java tree_sitter_rust tree_sitter_go)
|
||||
|
||||
add_executable(step521_test tests/step521_test.cpp)
|
||||
target_include_directories(step521_test PRIVATE src)
|
||||
target_link_libraries(step521_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)
|
||||
|
||||
72
editor/src/NotificationStatusRefresh.h
Normal file
72
editor/src/NotificationStatusRefresh.h
Normal file
@@ -0,0 +1,72 @@
|
||||
#pragma once
|
||||
// Step 521: Notification + Status Signaling Refresh
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
|
||||
enum class NotifyLevel {
|
||||
Info,
|
||||
Warn,
|
||||
Error,
|
||||
Critical
|
||||
};
|
||||
|
||||
struct NotificationStyle {
|
||||
float borderAlpha = 0.0f;
|
||||
float textContrast = 1.0f;
|
||||
float iconContrast = 1.0f;
|
||||
float defaultDurationMs = 0.0f;
|
||||
int maxStack = 5;
|
||||
};
|
||||
|
||||
struct NotificationMessage {
|
||||
NotifyLevel level = NotifyLevel::Info;
|
||||
std::string text;
|
||||
float createdMs = 0.0f;
|
||||
float durationMs = 0.0f;
|
||||
bool dismissed = false;
|
||||
};
|
||||
|
||||
class NotificationStatusRefresh {
|
||||
public:
|
||||
static NotificationStyle styleFor(NotifyLevel level) {
|
||||
switch (level) {
|
||||
case NotifyLevel::Info: return {0.28f, 5.4f, 3.8f, 2400.0f, 5};
|
||||
case NotifyLevel::Warn: return {0.34f, 5.6f, 4.0f, 3200.0f, 5};
|
||||
case NotifyLevel::Error: return {0.42f, 6.0f, 4.5f, 4200.0f, 6};
|
||||
case NotifyLevel::Critical: return {0.52f, 7.0f, 5.0f, 0.0f, 8};
|
||||
}
|
||||
return {0.3f, 5.0f, 3.5f, 2400.0f, 5};
|
||||
}
|
||||
|
||||
static bool shouldExpire(const NotificationMessage& m, float nowMs) {
|
||||
if (m.dismissed) return true;
|
||||
if (m.durationMs <= 0.0f) return false; // persistent critical
|
||||
return nowMs - m.createdMs >= m.durationMs;
|
||||
}
|
||||
|
||||
static std::vector<NotificationMessage> applyStackPolicy(
|
||||
const std::vector<NotificationMessage>& in,
|
||||
int maxStack) {
|
||||
if ((int)in.size() <= maxStack) return in;
|
||||
std::vector<NotificationMessage> out = in;
|
||||
out.erase(out.begin(), out.begin() + ((int)out.size() - maxStack));
|
||||
return out;
|
||||
}
|
||||
|
||||
static NotificationMessage makeMessage(NotifyLevel level,
|
||||
const std::string& text,
|
||||
float nowMs) {
|
||||
NotificationMessage m;
|
||||
m.level = level;
|
||||
m.text = text;
|
||||
m.createdMs = nowMs;
|
||||
m.durationMs = styleFor(level).defaultDurationMs;
|
||||
return m;
|
||||
}
|
||||
|
||||
static bool readable(const NotificationStyle& s) {
|
||||
return s.textContrast >= 4.5f && s.iconContrast >= 3.0f;
|
||||
}
|
||||
};
|
||||
133
editor/tests/step521_test.cpp
Normal file
133
editor/tests/step521_test.cpp
Normal file
@@ -0,0 +1,133 @@
|
||||
// Step 521: Notification + Status Signaling Refresh (12 tests)
|
||||
|
||||
#include "NotificationStatusRefresh.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_info_style_has_short_duration() {
|
||||
TEST(info_style_has_short_duration);
|
||||
auto s = NotificationStatusRefresh::styleFor(NotifyLevel::Info);
|
||||
CHECK(s.defaultDurationMs > 0 && s.defaultDurationMs < 3000, "info duration invalid");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_warn_duration_longer_than_info() {
|
||||
TEST(warn_duration_longer_than_info);
|
||||
auto a = NotificationStatusRefresh::styleFor(NotifyLevel::Info);
|
||||
auto b = NotificationStatusRefresh::styleFor(NotifyLevel::Warn);
|
||||
CHECK(b.defaultDurationMs > a.defaultDurationMs, "warn should last longer");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_error_duration_longer_than_warn() {
|
||||
TEST(error_duration_longer_than_warn);
|
||||
auto a = NotificationStatusRefresh::styleFor(NotifyLevel::Warn);
|
||||
auto b = NotificationStatusRefresh::styleFor(NotifyLevel::Error);
|
||||
CHECK(b.defaultDurationMs > a.defaultDurationMs, "error should last longer");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_critical_is_persistent_by_default() {
|
||||
TEST(critical_is_persistent_by_default);
|
||||
auto s = NotificationStatusRefresh::styleFor(NotifyLevel::Critical);
|
||||
CHECK(s.defaultDurationMs == 0.0f, "critical should be persistent");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_readability_passes_all_levels() {
|
||||
TEST(readability_passes_all_levels);
|
||||
bool ok = true;
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
if (!NotificationStatusRefresh::readable(
|
||||
NotificationStatusRefresh::styleFor(static_cast<NotifyLevel>(i)))) {
|
||||
ok = false;
|
||||
}
|
||||
}
|
||||
CHECK(ok, "one or more levels not readable");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_message_factory_uses_style_duration() {
|
||||
TEST(message_factory_uses_style_duration);
|
||||
auto m = NotificationStatusRefresh::makeMessage(NotifyLevel::Warn, "warn", 1000.0f);
|
||||
CHECK(m.durationMs == NotificationStatusRefresh::styleFor(NotifyLevel::Warn).defaultDurationMs,
|
||||
"factory should use style duration");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_expire_true_after_duration() {
|
||||
TEST(expire_true_after_duration);
|
||||
auto m = NotificationStatusRefresh::makeMessage(NotifyLevel::Info, "info", 0.0f);
|
||||
CHECK(NotificationStatusRefresh::shouldExpire(m, m.durationMs + 1.0f),
|
||||
"message should expire after duration");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_expire_false_before_duration() {
|
||||
TEST(expire_false_before_duration);
|
||||
auto m = NotificationStatusRefresh::makeMessage(NotifyLevel::Info, "info", 0.0f);
|
||||
CHECK(!NotificationStatusRefresh::shouldExpire(m, m.durationMs - 1.0f),
|
||||
"message should not expire early");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_dismissed_message_expires_immediately() {
|
||||
TEST(dismissed_message_expires_immediately);
|
||||
auto m = NotificationStatusRefresh::makeMessage(NotifyLevel::Critical, "crit", 0.0f);
|
||||
m.dismissed = true;
|
||||
CHECK(NotificationStatusRefresh::shouldExpire(m, 10.0f), "dismissed should expire");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_stack_policy_keeps_latest_messages() {
|
||||
TEST(stack_policy_keeps_latest_messages);
|
||||
std::vector<NotificationMessage> msgs;
|
||||
for (int i = 0; i < 8; ++i) msgs.push_back(NotificationStatusRefresh::makeMessage(NotifyLevel::Info, std::to_string(i), i));
|
||||
auto out = NotificationStatusRefresh::applyStackPolicy(msgs, 5);
|
||||
CHECK((int)out.size() == 5, "stack size should cap to 5");
|
||||
CHECK(out.front().text == "3" && out.back().text == "7", "should keep latest entries");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_stack_policy_no_change_when_under_limit() {
|
||||
TEST(stack_policy_no_change_when_under_limit);
|
||||
std::vector<NotificationMessage> msgs;
|
||||
for (int i = 0; i < 3; ++i) msgs.push_back(NotificationStatusRefresh::makeMessage(NotifyLevel::Info, std::to_string(i), i));
|
||||
auto out = NotificationStatusRefresh::applyStackPolicy(msgs, 5);
|
||||
CHECK((int)out.size() == 3, "stack should remain unchanged");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_critical_level_has_largest_stack_budget() {
|
||||
TEST(critical_level_has_largest_stack_budget);
|
||||
auto c = NotificationStatusRefresh::styleFor(NotifyLevel::Critical);
|
||||
auto i = NotificationStatusRefresh::styleFor(NotifyLevel::Info);
|
||||
CHECK(c.maxStack > i.maxStack, "critical stack budget should be higher");
|
||||
PASS();
|
||||
}
|
||||
|
||||
int main() {
|
||||
std::cout << "Step 521: Notification + Status Signaling Refresh\n";
|
||||
|
||||
test_info_style_has_short_duration(); // 1
|
||||
test_warn_duration_longer_than_info(); // 2
|
||||
test_error_duration_longer_than_warn(); // 3
|
||||
test_critical_is_persistent_by_default(); // 4
|
||||
test_readability_passes_all_levels(); // 5
|
||||
test_message_factory_uses_style_duration(); // 6
|
||||
test_expire_true_after_duration(); // 7
|
||||
test_expire_false_before_duration(); // 8
|
||||
test_dismissed_message_expires_immediately(); // 9
|
||||
test_stack_policy_keeps_latest_messages(); // 10
|
||||
test_stack_policy_no_change_when_under_limit(); // 11
|
||||
test_critical_level_has_largest_stack_budget(); // 12
|
||||
|
||||
std::cout << "\nResults: " << passed << "/" << (passed + failed) << " passed\n";
|
||||
return failed == 0 ? 0 : 1;
|
||||
}
|
||||
36
progress.md
36
progress.md
@@ -8419,3 +8419,39 @@ logic, and stable pointer/keyboard navigation semantics.
|
||||
- `editor/src/CompletionOverlayUXHardening.h` within header-size limit (`87` <= `600`)
|
||||
- `editor/tests/step520_test.cpp` within test-file size guidance (`123` lines)
|
||||
- Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md`
|
||||
|
||||
### Step 521: Notification + Status Signaling Refresh
|
||||
**Status:** PASS (12/12 tests)
|
||||
|
||||
Implements refreshed notification/status signaling semantics with consistent
|
||||
info/warn/error/critical behavior, readability guarantees, duration policies,
|
||||
and stack management rules including persistent critical signaling.
|
||||
|
||||
**Files added:**
|
||||
- `editor/src/NotificationStatusRefresh.h` - notification signaling module:
|
||||
- severity-specific style and duration policy
|
||||
- message factory with policy-derived timing
|
||||
- expiration logic with dismissal override and persistent critical support
|
||||
- stack-capping policy retaining newest messages
|
||||
- readability checks for text/icon contrast
|
||||
- `editor/tests/step521_test.cpp` - 12 tests covering:
|
||||
- duration progression by severity
|
||||
- persistent critical behavior
|
||||
- readability checks across levels
|
||||
- expire/no-expire timing boundaries
|
||||
- dismissed-message handling
|
||||
- stack truncation behavior and ordering
|
||||
|
||||
**Files modified:**
|
||||
- `editor/CMakeLists.txt` - `step521_test` target
|
||||
|
||||
**Verification run:**
|
||||
- `cmake -S editor -B editor/build-native` - PASS
|
||||
- `cmake --build editor/build-native --target step521_test` - PASS
|
||||
- `./editor/build-native/step521_test` - PASS (12/12)
|
||||
- `./editor/build-native/step520_test` - PASS (12/12) regression coverage
|
||||
|
||||
**Architecture gate check:**
|
||||
- `editor/src/NotificationStatusRefresh.h` within header-size limit (`72` <= `600`)
|
||||
- `editor/tests/step521_test.cpp` within test-file size guidance (`133` lines)
|
||||
- Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md`
|
||||
|
||||
Reference in New Issue
Block a user