feat(ui): implement UI/UX audit — i18n, HiDPI, theme, destructive-action & overflow fixes

Implements all 26 confirmed UI/UX audit findings plus the 4 dashboard/timeline
tile labels. Verified across full-node + Lite + Windows builds (ctest green) and
an adversarial diff review (one market-tab delete regression caught + fixed).

i18n coverage:
- Balance hero/quick-actions/toasts + dashboard & timeline tiles (Total Balance,
  Shielded, Transparent, Quick Send, Quick Receive, Click to open, Market)
- Send: Review Send / Cancel / Paste / view-only tooltip / memo byte counter
- Settings: Lite lifecycle errors, plaintext-RPC security warning, 8 toasts
- Mining pool-payout tooltip
- Resolve daemon_update_title double-assignment collision (new daemon_update_prompt_title)
- 23 new keys translated into all 8 locales; CJK subset font rebuilt

HiDPI: DPI-scale the Send amount bar, mining thread-tile clamp bounds, receive
loading skeleton, and sidebar notification-badge insets.

Light theme: theme-aware material::SurfaceOverlay() for balance bar tracks and
row-hover highlights; contacts active-pill foreground uses OnPrimary().

Destructive actions: arm/confirm for portfolio-group delete, saved pool/worker
remove, and avatar-image delete.

Interaction/overflow: route balance star/eye buttons and the console fold-toggle
through popup-safe guards; clip peer addr/subver; truncate the balance custom
label; measure Settings tool-button widths; mining stepper disabled-state
feedback; +/- stepper buttons restyled to match the thread tiles.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-18 17:54:15 -05:00
co-authored by Claude Opus 4.8
parent 0de44569b7
commit e24ca015d1
24 changed files with 599 additions and 148 deletions
+10 -8
View File
@@ -424,11 +424,13 @@ static void RenderAmountBar(ImDrawList* dl, double available, float innerW,
? std::clamp((float)((s_amount + s_fee) / available), 0.0f, 1.0f)
: 0.0f;
float maxBtnW = schema::UI().drawElement("tabs.send", "amount-bar-max-btn-width").size;
const float dp = Layout::dpiScale();
float maxBtnW = schema::UI().drawElement("tabs.send", "amount-bar-max-btn-width").size * dp;
float gap = Layout::spacingMd();
float barW = innerW - maxBtnW - gap;
if (barW < schema::UI().drawElement("tabs.send", "progress-bar-min-width").size) barW = schema::UI().drawElement("tabs.send", "progress-bar-min-width").size;
float barH = schema::UI().drawElement("tabs.send", "amount-bar-height").size;
float minBarW = schema::UI().drawElement("tabs.send", "progress-bar-min-width").size * dp;
if (barW < minBarW) barW = minBarW;
float barH = schema::UI().drawElement("tabs.send", "amount-bar-height").size * dp;
float barRound = barH * 0.5f;
ImVec2 barMin = ImGui::GetCursorScreenPos();
@@ -1000,7 +1002,7 @@ static void RenderActionButtons(App* app, float width, float vScale,
ImGui::BeginDisabled(!can_send);
char sendId[64];
snprintf(sendId, sizeof(sendId), "Review Send%s", suffix);
snprintf(sendId, sizeof(sendId), "%s##ReviewSend%s", TR("review_send"), suffix);
if (TactileButton(sendId, ImVec2(sendBtnW, btnH), S.resolveFont(S.button("tabs.send", "send-button").font))) {
s_show_confirm = true;
}
@@ -1020,7 +1022,7 @@ static void RenderActionButtons(App* app, float width, float vScale,
else if (total > available)
material::Tooltip("%s", TR("send_tooltip_exceeds_balance"));
else if (!sourceSpendable)
material::Tooltip("%s", "View-only address — no spending key, cannot send");
material::Tooltip("%s", TR("send_tooltip_view_only"));
else if (s_sending)
material::Tooltip("%s", TR("send_tooltip_in_progress"));
}
@@ -1033,7 +1035,7 @@ static void RenderActionButtons(App* app, float width, float vScale,
ImGui::PushStyleColor(ImGuiCol_Border, ImGui::ColorConvertU32ToFloat4(OnSurfaceDisabled()));
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, S.drawElement("tabs.send", "cancel-btn-border-size").size);
char clearId[64];
snprintf(clearId, sizeof(clearId), "Cancel%s", suffix);
snprintf(clearId, sizeof(clearId), "%s##Cancel%s", TR("cancel"), suffix);
if (TactileButton(clearId, ImVec2(cancelBtnW, btnH), S.resolveFont(S.button("tabs.send", "clear-button").font))) {
if (FormHasData()) {
s_clear_confirm_pending = true;
@@ -1377,7 +1379,7 @@ void RenderSendTab(App* app)
s_preview_text.c_str(), s_preview_text.c_str() + std::min(s_preview_text.size(), (size_t)S.drawElement("tabs.send", "paste-preview-max-chars").size));
}
if (TactileButton("Paste##to", ImVec2(pasteW, 0), S.resolveFont(S.button("tabs.send", "paste-button").font))) {
if (TactileButton((std::string(TR("paste")) + "##to").c_str(), ImVec2(pasteW, 0), S.resolveFont(S.button("tabs.send", "paste-button").font))) {
if (s_paste_previewing) {
// Commit the preview
snprintf(s_to_address, sizeof(s_to_address), "%s", s_preview_text.c_str());
@@ -1547,7 +1549,7 @@ void RenderSendTab(App* app)
size_t memo_len = strlen(s_memo);
size_t memoMax = (size_t)S.drawElement("business", "memo-max-length").size;
bool memoAtCap = memo_len + 1 >= memoMax;
snprintf(buf, sizeof(buf), "%zu / %zu bytes", memo_len, memoMax);
snprintf(buf, sizeof(buf), TR("byte_count_fmt"), memo_len, memoMax);
Type().textColored(TypeStyle::Caption, memoAtCap ? Warning() : OnSurfaceDisabled(), buf);
}