From d189ee5ddbc1695a9cb03d1ac405633066bcc440 Mon Sep 17 00:00:00 2001 From: DanS Date: Sat, 11 Jul 2026 22:27:44 -0500 Subject: [PATCH] fix(ui): readable error text on dark modal backdrops MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Material --error reds are muted by design, so error text drawn with the theme Error() color read low-contrast on the (now deeper) dark modal backdrops — the audit flagged the seed-migration warning/error lines on dark / dragonx. Add material::ReadableError(): the theme error lifted toward a legible light red on dark themes, unchanged on light themes (where it's already a dark red that reads on a light background). Apply it to the modal error-TEXT sites (lite first-run, seed backup, seed migration). This is targeted — it never touches the app-wide Error() used by toasts / badges / status dots — so nothing else shifts. Verified on the sweep: the "node too old" warning (dark) and "daemon did not respond" error (dragonx) now read clearly; light themes are unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/app.cpp | 24 ++++++++++++------------ src/ui/material/draw_helpers.h | 13 +++++++++++++ 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/src/app.cpp b/src/app.cpp index 5d11a6a..6abde46 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -2471,7 +2471,7 @@ void App::renderLiteFirstRunPrompt() ImGui::PopFont(); ImGui::Spacing(); ImGui::PushTextWrapPos(ImGui::GetCursorPosX() + 380.0f); - ImGui::PushStyleColor(ImGuiCol_Text, ui::material::Error()); + ImGui::PushStyleColor(ImGuiCol_Text, ui::material::ReadableError()); ImGui::TextUnformatted("These 24 words are the ONLY way to restore your wallet. " "Write them down in order, store them offline, and never share " "them. If you lose them, your funds are gone forever."); @@ -2517,7 +2517,7 @@ void App::renderLiteFirstRunPrompt() if (skipConfirm) { ImGui::Spacing(); ImGui::PushTextWrapPos(ImGui::GetCursorPosX() + 380.0f); - ImGui::PushStyleColor(ImGuiCol_Text, ui::material::Error()); + ImGui::PushStyleColor(ImGuiCol_Text, ui::material::ReadableError()); ImGui::TextUnformatted("You have not backed up your seed — funds could be lost. " "Skip anyway?"); ImGui::PopStyleColor(); @@ -2537,7 +2537,7 @@ void App::renderLiteFirstRunPrompt() ImGui::TextDisabled("Progress: %d / %d", progress, (int)words.size()); if (ImGui::GetTime() < wrongFlashUntil) { ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Text, ui::material::Error()); + ImGui::PushStyleColor(ImGuiCol_Text, ui::material::ReadableError()); ImGui::TextUnformatted(" — that's not the next word"); ImGui::PopStyleColor(); } @@ -2583,7 +2583,7 @@ void App::renderLiteFirstRunPrompt() if (skipConfirm) { ImGui::Spacing(); ImGui::PushTextWrapPos(ImGui::GetCursorPosX() + 380.0f); - ImGui::PushStyleColor(ImGuiCol_Text, ui::material::Error()); + ImGui::PushStyleColor(ImGuiCol_Text, ui::material::ReadableError()); ImGui::TextUnformatted("You have not backed up your seed — funds could be lost. " "Skip anyway?"); ImGui::PopStyleColor(); @@ -2623,7 +2623,7 @@ void App::renderLiteFirstRunPrompt() if (!restoreErr.empty()) { ImGui::Spacing(); - ImGui::PushStyleColor(ImGuiCol_Text, ui::material::Error()); + ImGui::PushStyleColor(ImGuiCol_Text, ui::material::ReadableError()); ImGui::PushTextWrapPos(ImGui::GetCursorPosX() + 380.0f); ImGui::TextUnformatted(restoreErr.c_str()); ImGui::PopTextWrapPos(); @@ -3080,7 +3080,7 @@ void App::renderSeedBackupDialog() } else if (!seed_backup_phrase_.empty()) { ImGui::TextWrapped("%s", TR("seed_backup_intro")); ImGui::Spacing(); - ImGui::PushStyleColor(ImGuiCol_Text, ui::material::Error()); + ImGui::PushStyleColor(ImGuiCol_Text, ui::material::ReadableError()); ImGui::TextWrapped("%s", TR("seed_backup_warning")); ImGui::PopStyleColor(); ImGui::Spacing(); @@ -3188,7 +3188,7 @@ void App::renderSeedMigrationDialog() if (ui::material::StyledButton(TR("close"), ImVec2(120 * dp, 0))) close(); break; case SeedMigrationPrecheck::DaemonTooOld: - ImGui::PushStyleColor(ImGuiCol_Text, ui::material::Error()); + ImGui::PushStyleColor(ImGuiCol_Text, ui::material::ReadableError()); ImGui::TextWrapped("%s", TR("mig_daemon_too_old")); ImGui::PopStyleColor(); ImGui::Spacing(); ImGui::Separator(); @@ -3223,7 +3223,7 @@ void App::renderSeedMigrationDialog() break; case SeedMigrationStep::ShowSeed: { - ImGui::PushStyleColor(ImGuiCol_Text, ui::material::Error()); + ImGui::PushStyleColor(ImGuiCol_Text, ui::material::ReadableError()); ImGui::TextWrapped("%s", TR("mig_seed_warning")); ImGui::PopStyleColor(); ImGui::Spacing(); @@ -3320,7 +3320,7 @@ void App::renderSeedMigrationDialog() ImGui::TextWrapped("%s%s", TR("mig_to"), seed_migration_dest_.c_str()); if (state_.isLocked()) { ImGui::Spacing(); - ImGui::PushStyleColor(ImGuiCol_Text, ui::material::Error()); + ImGui::PushStyleColor(ImGuiCol_Text, ui::material::ReadableError()); ImGui::TextWrapped("%s", TR("mig_unlock_first")); ImGui::PopStyleColor(); } @@ -3369,7 +3369,7 @@ void App::renderSeedMigrationDialog() if (ui::material::StyledButton(TR("mig_adopt"), ImVec2(180 * dp, 0))) beginAdoptSeedWallet(); } else if (confirmed && !legacyEmpty) { - ImGui::PushStyleColor(ImGuiCol_Text, ui::material::Error()); + ImGui::PushStyleColor(ImGuiCol_Text, ui::material::ReadableError()); ImGui::TextWrapped("%s", TR("mig_remainder")); ImGui::PopStyleColor(); if (ui::material::StyledButton(TR("mig_sweep_remaining"), ImVec2(180 * dp, 0))) @@ -3404,7 +3404,7 @@ void App::renderSeedMigrationDialog() ImGui::TextWrapped("%s", TR("mig_done_detail")); if (!seed_migration_status_.empty()) { // a non-fatal warning (e.g. the daemon didn't restart) ImGui::Spacing(); - ImGui::PushStyleColor(ImGuiCol_Text, ui::material::Error()); + ImGui::PushStyleColor(ImGuiCol_Text, ui::material::ReadableError()); ImGui::TextWrapped("%s", seed_migration_status_.c_str()); ImGui::PopStyleColor(); } @@ -3415,7 +3415,7 @@ void App::renderSeedMigrationDialog() break; case SeedMigrationStep::Error: - ImGui::PushStyleColor(ImGuiCol_Text, ui::material::Error()); + ImGui::PushStyleColor(ImGuiCol_Text, ui::material::ReadableError()); ImGui::TextWrapped("%s", seed_migration_status_.c_str()); ImGui::PopStyleColor(); ImGui::Spacing(); diff --git a/src/ui/material/draw_helpers.h b/src/ui/material/draw_helpers.h index 94f834a..fc66aee 100644 --- a/src/ui/material/draw_helpers.h +++ b/src/ui/material/draw_helpers.h @@ -44,6 +44,19 @@ inline bool IsLightTheme() { return (0.299f * r + 0.587f * g + 0.114f * b) > 0.5f; } +// Theme error color tuned for use as TEXT. The Material --error reds are muted, so on a dark modal +// backdrop they read low-contrast; lift them toward a legible light red on dark themes. Light themes +// keep the theme error (already a dark red readable on a light background). +inline ImU32 ReadableError() { + ImU32 e = Error(); + if (IsLightTheme()) return e; + const float m = 0.35f; // blend toward white + int r = (int)(((e >> IM_COL32_R_SHIFT) & 0xFF) * (1 - m) + 255 * m); + int g = (int)(((e >> IM_COL32_G_SHIFT) & 0xFF) * (1 - m) + 255 * m); + int b = (int)(((e >> IM_COL32_B_SHIFT) & 0xFF) * (1 - m) + 255 * m); + return IM_COL32(r, g, b, (e >> IM_COL32_A_SHIFT) & 0xFF); +} + // Animated "loading" ellipsis: "", ".", "..", "..." cycling on a ~3Hz phase. inline const char* LoadingDots() { int n = ((int)(ImGui::GetTime() * 3.0f)) % 4;