Step 521: refresh notification and status signaling semantics

This commit is contained in:
Bill
2026-02-17 09:04:27 -07:00
parent b9f108e705
commit ac584065f8
4 changed files with 250 additions and 0 deletions

View File

@@ -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)

View 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;
}
};

View 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;
}

View File

@@ -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`