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
parent 0de44569b7
commit e24ca015d1
24 changed files with 599 additions and 148 deletions

View File

@@ -466,7 +466,8 @@ void RenderSharedAddressList(App* app, float listH, float availW,
{
const auto& starRect = rowLayout.favoriteButton;
ImVec2 bMin(starRect.x, starRect.y), bMax(starRect.x + starRect.width, starRect.y + starRect.height);
bool bHov = ImGui::IsMouseHoveringRect(bMin, bMax);
// material::IsRectHovered so the button inherits overlay/popup input blocking
bool bHov = material::IsRectHovered(bMin, bMax);
dl->AddRectFilled(bMin, bMax, row.favorite ? favGoldFill : (bHov ? btnFillHov : btnFill), btnRound);
dl->AddRect(bMin, bMax, row.favorite ? favGoldBorder : (bHov ? btnBorderHov : btnBorder), btnRound, 0, 1.0f * dp);
ImFont* iconFont = Type().iconSmall();
@@ -492,7 +493,8 @@ void RenderSharedAddressList(App* app, float listH, float availW,
if (showEye) {
const auto& eyeRect = rowLayout.visibilityButton;
ImVec2 bMin(eyeRect.x, eyeRect.y), bMax(eyeRect.x + eyeRect.width, eyeRect.y + eyeRect.height);
bool bHov = ImGui::IsMouseHoveringRect(bMin, bMax);
// material::IsRectHovered so the button inherits overlay/popup input blocking
bool bHov = material::IsRectHovered(bMin, bMax);
dl->AddRectFilled(bMin, bMax, bHov ? btnFillHov : btnFill, btnRound);
dl->AddRect(bMin, bMax, bHov ? btnBorderHov : btnBorder, btnRound, 0, 1.0f * dp);
ImFont* iconFont = Type().iconSmall();
@@ -539,12 +541,24 @@ void RenderSharedAddressList(App* app, float listH, float availW,
snprintf(typeBuf, sizeof(typeBuf), "%s%s%s", typeLabel, hiddenTag, miningTag);
dl->AddText(capFont, capFont->LegacySize, ImVec2(labelX, cy), typeCol, typeBuf);
// User label next to type
// User label next to type — clip to the gap before the right-aligned
// balance so a long custom label can't overrun the balance on this line
// (mirrors the address-line width guard below).
if (!row.label.empty()) {
float typeLabelW = capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX, 0, typeBuf).x;
dl->AddText(capFont, capFont->LegacySize,
ImVec2(labelX + typeLabelW + Layout::spacingLg(), cy),
OnSurfaceMedium(), row.label.c_str());
float userLabelX = labelX + typeLabelW + Layout::spacingLg();
// Balance is drawn right-aligned at contentRight; recompute its left edge here.
char balBufPeek[32];
snprintf(balBufPeek, sizeof(balBufPeek), "%.8f", addr.balance);
float balW = body2->CalcTextSizeA(body2->LegacySize, FLT_MAX, 0, balBufPeek).x;
float userLabelAvailW = (contentRight - balW - Layout::spacingMd()) - userLabelX;
std::string userLabel = material::TruncateToWidth(
row.label, capFont, capFont->LegacySize, userLabelAvailW);
if (userLabelAvailW > 0.0f) {
dl->AddText(capFont, capFont->LegacySize,
ImVec2(userLabelX, cy),
OnSurfaceMedium(), userLabel.c_str());
}
}
}
@@ -841,7 +855,7 @@ void RenderSharedRecentTx(App* app, float recentH, float availW, float hs, float
float rowW = ImGui::GetContentRegionAvail().x;
ImVec2 rowEnd(rowPos.x + rowW, rowPos.y + rowH);
if (material::IsRectHovered(rowPos, rowEnd)) {
dl->AddRectFilled(rowPos, rowEnd, IM_COL32(255, 255, 255, 15), S.drawElement("tabs.balance", "row-hover-rounding").sizeOr(4.0f));
dl->AddRectFilled(rowPos, rowEnd, SurfaceOverlay(15), S.drawElement("tabs.balance", "row-hover-rounding").sizeOr(4.0f));
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
if (ImGui::IsMouseClicked(0))
app->setCurrentPage(NavPage::History);
@@ -868,7 +882,7 @@ void RenderSyncBar(App* app, ImDrawList* dl, float vs) {
ImVec2 barPos = ImGui::GetCursorScreenPos();
dl->AddRectFilled(barPos,
ImVec2(barPos.x + barW, barPos.y + barH),
IM_COL32(255, 255, 255, 15), 1.0f * dp);
SurfaceOverlay(15), 1.0f * dp);
dl->AddRectFilled(barPos,
ImVec2(barPos.x + barW * prog, barPos.y + barH),
WithAlpha(Warning(), 200), 1.0f * dp);