From ac584065f8966059a70d89beceba3e7fc160b133 Mon Sep 17 00:00:00 2001 From: Bill Date: Tue, 17 Feb 2026 09:04:27 -0700 Subject: [PATCH] Step 521: refresh notification and status signaling semantics --- editor/CMakeLists.txt | 9 ++ editor/src/NotificationStatusRefresh.h | 72 +++++++++++++ editor/tests/step521_test.cpp | 133 +++++++++++++++++++++++++ progress.md | 36 +++++++ 4 files changed, 250 insertions(+) create mode 100644 editor/src/NotificationStatusRefresh.h create mode 100644 editor/tests/step521_test.cpp diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 0f19032..105f896 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -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) diff --git a/editor/src/NotificationStatusRefresh.h b/editor/src/NotificationStatusRefresh.h new file mode 100644 index 0000000..e0eb728 --- /dev/null +++ b/editor/src/NotificationStatusRefresh.h @@ -0,0 +1,72 @@ +#pragma once +// Step 521: Notification + Status Signaling Refresh + +#include +#include +#include + +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 applyStackPolicy( + const std::vector& in, + int maxStack) { + if ((int)in.size() <= maxStack) return in; + std::vector 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; + } +}; diff --git a/editor/tests/step521_test.cpp b/editor/tests/step521_test.cpp new file mode 100644 index 0000000..5e6ac28 --- /dev/null +++ b/editor/tests/step521_test.cpp @@ -0,0 +1,133 @@ +// Step 521: Notification + Status Signaling Refresh (12 tests) + +#include "NotificationStatusRefresh.h" + +#include + +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(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 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 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; +} diff --git a/progress.md b/progress.md index 89a9480..9e12b1c 100644 --- a/progress.md +++ b/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`