feat(ui): settings polish — button retune, daemon card, RPC 2-row, chat preview

Settings tabs brought closer to the approved mockup:
- ActionButton/renderCardButton retune (settings-scoped): 7px radius, 9px
  padX, Primary → accent-outline chip, Secondary/card buttons more defined.
- Daemon-binary card: compact status right-aligned on the DAEMON BINARY
  heading (Up to date / Version differs / Not installed), filled/rounded
  status box, neutral danger divider (was alarming red), roomier spacing.
- RPC Connection: two-row column-aligned layout (Host | Port, then
  Username | Password) so the password no longer clips off the card edge.
- Chat settings tab: live conversation preview below the Appearance /
  Messaging cards; "Focus input on open" checkbox reflowed onto the console
  color-toggle row.
- Debug Options: "Current theme only" toggle restricts either screenshot
  sweep to the active theme instead of cycling every skin.
- Tabs fill the full content width (content-max-width cap disabled) and the
  sidebar nav panel centers within the true visible area.
- i18n: new keys for the above (untranslated keys fall back to English).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-20 16:59:03 -05:00
co-authored by Claude Opus 4.8
parent b37d3d97b6
commit d0bd55b9c1
19 changed files with 1082 additions and 990 deletions
+13 -24
View File
@@ -1795,32 +1795,21 @@ void App::render()
sbStatus.unconfirmedTxCount = static_cast<int>(unconfirmedTxids.size());
}
// Sidebar margins from ui.toml schema (DPI-scaled like all sidebar values)
const float sbMarginTop = sbde("margin-top", 0.0f);
const float sbMarginBottom = sbde("margin-bottom", 0.0f);
const float sbMinHeight = sbde("min-height", 360.0f);
// Sidebar minimum height from ui.toml schema (DPI-scaled).
const float sbMinHeight = sbde("min-height", 360.0f);
// Ensure sidebar is tall enough to fit all buttons — shrink margins if needed
float sidebarH = contentH - sbMarginTop - sbMarginBottom;
float effectiveMarginTop = sbMarginTop;
if (sidebarH < sbMinHeight) {
float available = contentH - sbMinHeight;
if (available > 0.0f) {
float ratio = available / (sbMarginTop + sbMarginBottom);
effectiveMarginTop = sbMarginTop * ratio;
} else {
effectiveMarginTop = 0.0f;
}
sidebarH = std::max(contentH - effectiveMarginTop, sbMinHeight);
}
// Sidebar navigation
// Save cursor Y before applying sidebar margin so the content area
// (placed via SameLine) starts at the original row position, not the
// margin-shifted one.
// Save cursor Y before the sidebar so the content area (restored below) starts at the original row.
float preSidebarCursorY = ImGui::GetCursorPosY();
if (effectiveMarginTop > 0.0f)
ImGui::SetCursorPosY(preSidebarCursorY + effectiveMarginTop);
// Size the sidebar to span from its own top down to the status-bar top, so the nav panel centers
// within the TRUE visible area (equal top/bottom gaps). Do NOT derive it from contentH (inset by the
// content-area's edge-fade margins) and do NOT apply the legacy sidebar margin-top/-bottom (they are
// asymmetric, -12 / +40, and pushed the panel upward). Window-local reference (matches how the status
// bar is positioned — GetWindowPos/GetWindowSize, not GetMainViewport) so it is correct on every platform.
float sbWindowBottom = ImGui::GetWindowPos().y + ImGui::GetWindowSize().y;
float sbStatusTopY = sbWindowBottom - statusBarH - mainPadBot;
float sbChildTopY = ImGui::GetCursorScreenPos().y;
float sidebarH = std::max(sbMinHeight, sbStatusTopY - sbChildTopY);
bool prevCollapsed = sidebar_collapsed_;
{
PERF_SCOPE("Render.Sidebar");
+4
View File
@@ -408,6 +408,9 @@ public:
// each under every skin. Output: <config>/screenshots-full/<surface>/<skin>.png + an index.
void startFullUiSweep();
std::string screenshotFullDir() const;
// Debug option: restrict either sweep to just the currently-active theme instead of cycling all.
bool sweepCurrentThemeOnly() const { return sweep_current_theme_only_; }
void setSweepCurrentThemeOnly(bool v) { sweep_current_theme_only_ = v; }
bool isScreenshotSweeping() const { return screenshot_sweep_active_; }
bool wantsScreenshotThisFrame() const { return sweep_capture_this_frame_; }
const std::string& screenshotSweepPath() const { return sweep_current_path_; }
@@ -1114,6 +1117,7 @@ private:
// Debug screenshot sweep state.
bool screenshot_sweep_active_ = false;
bool sweep_current_theme_only_ = false; // Debug Options: sweep only the active theme
bool sweep_capture_this_frame_ = false;
int sweep_skin_idx_ = 0;
int sweep_settle_frames_ = 0; // frames to let a new skin/surface settle before capture
+4
View File
@@ -704,6 +704,10 @@ void App::startSweepImpl(bool full)
if (sk.valid) sweep_skins_.push_back(sk.id);
if (sweep_skins_.empty()) return;
// Debug Options "Current theme only": sweep just the active skin instead of cycling every theme.
if (sweep_current_theme_only_)
sweep_skins_.assign(1, ui::schema::SkinManager::instance().activeSkinId());
sweep_full_ = full;
if (full) { capture_mode_ = true; installDemoWalletData(); }
buildSweepCatalog();
+7 -6
View File
@@ -173,11 +173,11 @@ inline float kSidePanelMinWidth() { return schema::UI().drawElement("panels",
inline float kSidePanelMaxWidth() { return schema::UI().drawElement("panels", "side-panel").getFloat("max-width", 450.0f) * dpiScale(); }
inline float kSidePanelWidthRatio() { return schema::UI().drawElement("panels", "side-panel").getFloat("width-ratio", 0.4f); }
// Overall content-column cap: the max width a tab's content should occupy before it is centered in wider
// windows. Prevents cards/forms/tables (which all derive their size from the content child's width) from
// stretching edge-to-edge at wide/ultrawide widths. <= 0 disables (fill full width). Tunable via ui.toml
// [layout] content-max-width; default is generous so data-dense screens stay comfortable.
inline float kContentMaxWidth() { return schema::UI().drawElement("layout", "content-max-width").sizeOr(1600.0f) * dpiScale(); }
// Overall content-column cap: the max width a tab's content occupies before it is centered in wider
// windows. <= 0 disables the cap so tab content fills ALL available horizontal width (the default —
// requested so large windows don't leave a big empty gutter on the right). Set a positive
// ui.toml [layout] content-max-width to re-enable a centered readable column.
inline float kContentMaxWidth() { return schema::UI().drawElement("layout", "content-max-width").sizeOr(0.0f) * dpiScale(); }
// Shared compose-card envelope for the Send + Receive tabs (and any tab wanting the same box): fill the
// available column up to the content-max-width cap, then center the leftover as margin. Both tabs MUST
@@ -186,7 +186,8 @@ inline float kContentMaxWidth() { return schema::UI().drawElement("layout", "con
// Receive on any window wider than ~860dp. Returns {width, offsetX} in the same units as availW.
struct CardBox { float width; float offsetX; };
inline CardBox mainComposeCardBox(float availW) {
float w = std::min(availW, kContentMaxWidth());
float cap = kContentMaxWidth();
float w = (cap > 0.0f) ? std::min(availW, cap) : availW; // cap <= 0 -> fill full width
return CardBox{ w, std::max(0.0f, (availW - w) * 0.5f) };
}
+8 -6
View File
@@ -107,7 +107,7 @@ inline float ActionButtonWidth(const char* label, const char* icon, float minWid
ImFont* lf = Type().button();
ImFont* icf = Type().iconSmall();
const float dp = Layout::dpiScale();
const float padX = 12.0f * dp, gap = 6.0f * dp;
const float padX = 9.0f * dp, gap = 6.0f * dp; // mockup .btn padding: 9px horizontal
const float labelW = lf->CalcTextSizeA(lf->LegacySize, FLT_MAX, 0, label).x;
const float iconW = (icon && icon[0] && icf) ? icf->CalcTextSizeA(icf->LegacySize, FLT_MAX, 0, icon).x : 0.0f;
const float w = padX * 2.0f + iconW + (iconW > 0.0f ? gap : 0.0f) + labelW;
@@ -133,19 +133,21 @@ inline bool ActionButton(const char* id, const char* label, const char* icon, Ac
if (hov) ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
ImDrawList* dl = ImGui::GetWindowDrawList();
const ImVec2 pMax(pos.x + w, pos.y + h);
const float round = ImGui::GetStyle().FrameRounding;
const float round = 7.0f * dp; // mockup .btn radius: 7px (softer than the global 4px frame)
const float a = ImGui::GetStyle().Alpha; // BeginDisabled() lowers this
ImU32 bg = 0, border = 0, fg = OnSurface();
bool glass = false;
switch (tier) {
case ActionTier::Primary:
bg = WithAlpha(Primary(), act ? 255 : (hov ? 245 : 220));
fg = IM_COL32(255, 255, 255, 240);
// Mockup .btn.acc: a dark accent-tinted chip with accent TEXT — not a bright filled button.
bg = WithAlpha(Primary(), hov ? 52 : 38);
border = WithAlpha(Primary(), hov ? 150 : 110);
fg = Primary();
break;
case ActionTier::Secondary:
bg = WithAlpha(OnSurface(), hov ? 26 : 16);
border = WithAlpha(OnSurface(), 40);
bg = WithAlpha(OnSurface(), hov ? 30 : 20);
border = WithAlpha(OnSurface(), 48);
glass = true;
fg = OnSurface();
break;
File diff suppressed because it is too large Load Diff
+7 -5
View File
@@ -538,11 +538,6 @@ inline bool RenderSidebar(NavPage& current, float sidebarWidth, float contentHei
float exitRelY = curY + bottomPadding;
float panelH = exitRelY + stripH;
// Vertical centering — offset so panel is centered in the child window
float centerOffset = std::max(glassMarginY, (contentHeight - panelH) * 0.5f);
if (centerOffset + panelH > contentHeight)
centerOffset = std::max(0.0f, contentHeight - panelH);
// ===================================================================
// PASS 2: Render using computed positions
// ===================================================================
@@ -552,6 +547,13 @@ inline bool RenderSidebar(NavPage& current, float sidebarWidth, float contentHei
ImDrawList* dl = ImGui::GetWindowDrawList();
ImVec2 wp = ImGui::GetWindowPos();
// Vertical centering — center the panel within the child. app.cpp sizes the child (contentHeight)
// to the visible area (child top -> status-bar top) using window-local geometry, so this yields
// equal top/bottom gaps at any height on every platform, no viewport dependency.
float centerOffset = std::max(glassMarginY, (contentHeight - panelH) * 0.5f);
if (centerOffset + panelH > contentHeight)
centerOffset = std::max(0.0f, contentHeight - panelH);
float panelLeft = wp.x + glassMarginL;
float panelRight = wp.x + sidebarWidth - glassMarginR;
float panelTopY = wp.y + centerOffset;
+51 -4
View File
@@ -1885,20 +1885,37 @@ void ResetChatTab()
s_show_chat_settings = false;
}
void RenderChatSettingsControls(App* app, float contentWidth)
void RenderChatSettingsControls(App* app, float contentWidth, bool drawCards)
{
auto* st = app ? app->settings() : nullptr;
if (!st) return;
const float dp = Layout::dpiScale();
const float ctrlW = 250.0f * dp; // control column width (fits a 3-segment control comfortably)
const float rowGap = 5.0f * dp;
const float rowGap = 10.0f * dp;
// Optionally paint two glass cards (Appearance | Messaging) around our own two columns so the
// Settings tab matches the mockup's card-per-group layout. The chat modal passes drawCards=false
// and keeps its plain single-surface layout — the controls themselves are identical either way.
ImDrawList* cardDL = ImGui::GetWindowDrawList();
const float cardPad = drawCards ? Layout::cardInnerPadding() : 0.0f;
material::GlassPanelSpec cardSpec; cardSpec.rounding = Layout::glassRounding();
float cardTopScr = 0.0f, cardBaseXScr = 0.0f, cardLeftBotScr = 0.0f;
if (drawCards) {
cardTopScr = ImGui::GetCursorScreenPos().y;
cardBaseXScr = ImGui::GetCursorScreenPos().x;
cardDL->ChannelsSplit(2);
cardDL->ChannelsSetCurrent(1);
ImGui::SetCursorScreenPos(ImVec2(cardBaseXScr, cardTopScr + cardPad));
ImGui::Indent(cardPad);
}
// Right-align controls to the row's true right edge. The Settings tab renders us inside a GlassCard
// whose content region isn't narrowed to the card padding, so it passes an explicit contentWidth;
// the chat modal's dialog content region is correct, so it passes 0 (auto).
// leftX/rowW define the current column the rows lay out in; retargeted below
// to split Appearance | Messaging into two columns when the card is wide.
float leftX = ImGui::GetCursorPosX();
float rowW = (contentWidth > 0.0f) ? contentWidth : ImGui::GetContentRegionAvail().x;
float rowW = drawCards ? (contentWidth - 2.0f * cardPad)
: ((contentWidth > 0.0f) ? contentWidth : ImGui::GetContentRegionAvail().x);
// Label left, control right-aligned within [leftX, leftX+rowW]. Leaves the cursor at the control origin.
auto beginRow = [&](const char* label) {
@@ -1966,7 +1983,7 @@ void RenderChatSettingsControls(App* app, float contentWidth)
// Two internal columns when the card is wide enough: Appearance on the left,
// Messaging on the right — fills the width and roughly halves the height.
// (Mirrors the Node & Security card.) Narrow (the chat modal) stays single-column.
const float chatColGap = 24.0f * dp;
const float chatColGap = drawCards ? (Layout::cardGap() + 2.0f * cardPad) : (24.0f * dp);
const bool chatTwoCol = rowW > 760.0f * dp;
const float chatColW = chatTwoCol ? (rowW - chatColGap) * 0.5f : rowW;
const float chatBaseLeftX = leftX;
@@ -2023,6 +2040,7 @@ void RenderChatSettingsControls(App* app, float contentWidth)
// line-start holds the column; retarget leftX so controls right-align in it).
if (chatTwoCol) {
chatLeftBottomY = ImGui::GetCursorPosY();
cardLeftBotScr = ImGui::GetCursorScreenPos().y; // left column bottom (screen), for its card panel
ImGui::SetCursorPosY(chatTopY);
ImGui::Indent(chatColW + chatColGap);
leftX = chatBaseLeftX + chatColW + chatColGap;
@@ -2056,12 +2074,41 @@ void RenderChatSettingsControls(App* app, float contentWidth)
}
// Close the two-column band: un-indent and drop below the taller column.
const float cardRightBotScr = ImGui::GetCursorScreenPos().y; // right (or only) column bottom, screen
if (chatTwoCol) {
ImGui::Unindent(chatColW + chatColGap);
const float chatRightBottomY = ImGui::GetCursorPosY();
ImGui::SetCursorPosX(chatBaseLeftX);
ImGui::SetCursorPosY(std::max(chatLeftBottomY, chatRightBottomY));
}
// Paint the glass card(s) behind the content, then merge the channels.
if (drawCards) {
ImGui::Unindent(cardPad);
cardDL->ChannelsSetCurrent(0);
const float cardW = (contentWidth - Layout::cardGap()) * 0.5f;
if (chatTwoCol) {
const float eqBot = std::max(cardLeftBotScr, cardRightBotScr); // equal-height cards (mockup grid stretch)
material::DrawGlassPanel(cardDL, ImVec2(cardBaseXScr, cardTopScr),
ImVec2(cardBaseXScr + cardW, eqBot + cardPad), cardSpec);
material::DrawGlassPanel(cardDL, ImVec2(cardBaseXScr + cardW + Layout::cardGap(), cardTopScr),
ImVec2(cardBaseXScr + contentWidth, eqBot + cardPad), cardSpec);
} else {
material::DrawGlassPanel(cardDL, ImVec2(cardBaseXScr, cardTopScr),
ImVec2(cardBaseXScr + contentWidth, cardRightBotScr + cardPad), cardSpec);
}
cardDL->ChannelsMerge();
const float botScr = chatTwoCol ? std::max(cardLeftBotScr, cardRightBotScr) : cardRightBotScr;
// Reserve the card footprint with a Dummy so the parent scroll region grows to include it
// (a bare SetCursorScreenPos past content warns in ImGui).
ImGui::SetCursorScreenPos(ImVec2(cardBaseXScr, cardTopScr));
ImGui::Dummy(ImVec2(contentWidth, (botScr - cardTopScr) + cardPad));
// Live conversation preview below the two cards (Settings tab only — the chat modal renders
// its own preview column beside these controls, so it passes drawCards=false and skips this).
ImGui::Dummy(ImVec2(0.0f, Layout::spacingMd()));
RenderChatSettingsPreview(app, contentWidth);
}
}
} // namespace ui
+1 -1
View File
@@ -34,7 +34,7 @@ void RenderChatTab(App* app);
* width from the Settings tab (whose GlassCard doesn't narrow the content region). 0 = auto
* (use the current content region, correct inside the chat modal's dialog).
*/
void RenderChatSettingsControls(App* app, float contentWidth = 0.0f);
void RenderChatSettingsControls(App* app, float contentWidth = 0.0f, bool drawCards = false);
/**
* @brief Securely wipe the Chat tab's UI-local state (composer / new-conversation
+37 -20
View File
@@ -460,15 +460,23 @@ void I18n::loadBuiltinEnglish()
// Settings sections
strings_["appearance"] = "APPEARANCE";
strings_["theme_language"] = "THEME & LANGUAGE";
strings_["scale_effects"] = "SCALE & EFFECTS";
strings_["advanced_effects"] = "Advanced Effects...";
strings_["tools_actions"] = "Tools & Actions...";
strings_["tools_actions_hdr"] = "TOOLS & ACTIONS";
strings_["wallet_options_hdr"] = "OPTIONS";
strings_["wallet_diagnostics_hdr"] = "DIAGNOSTICS";
strings_["wallet"] = "WALLET";
strings_["node_security"] = "NODE & SECURITY";
strings_["node"] = "NODE";
strings_["security"] = "SECURITY";
strings_["explorer_section"] = "EXPLORER";
strings_["explorer_urls_hdr"] = "URLS";
strings_["about"] = "About";
strings_["backup_data"] = "BACKUP & DATA";
strings_["backup_col_import"] = "IMPORT & RESTORE";
strings_["backup_col_backup"] = "BACKUP";
strings_["backup_col_export"] = "EXPORT";
strings_["balance_layout"] = "Balance Layout";
strings_["low_spec_mode"] = "Low-spec mode";
strings_["simple_background"] = "Simple background";
@@ -496,9 +504,11 @@ void I18n::loadBuiltinEnglish()
strings_["screenshot_sweep"] = "Run screenshot sweep";
strings_["screenshot_sweep_full"] = "Full UI sweep";
strings_["screenshot_open_dir"] = "Open location";
strings_["sweep_current_theme_only"] = "Current theme only";
strings_["tt_sweep_current_theme_only"] = "Sweep only the active theme instead of cycling every theme";
strings_["screenshot_sweep_desc"] = "Cycles every theme across every tab and saves a screenshot of each into per-tab subfolders under the config directory's screenshots folder (overwriting the previous sweep). Runs for a few seconds.";
strings_["mine_when_idle"] = "Mine when idle";
strings_["setup_wizard"] = "Run Setup Wizard...";
strings_["setup_wizard"] = "Run Setup Wizard…";
// RPC / Explorer settings
strings_["rpc_connection"] = "RPC Connection...";
@@ -512,21 +522,21 @@ void I18n::loadBuiltinEnglish()
strings_["fetch_prices"] = "Fetch price data from CoinGecko";
strings_["block_explorer"] = "Block Explorer";
strings_["test_connection"] = "Test Connection";
strings_["rescan"] = "Rescan Blockchain";
strings_["rescan"] = "Rescan";
// Settings: buttons
strings_["settings_address_book"] = "Address Book...";
strings_["settings_validate_address"] = "Validate Address...";
strings_["settings_request_payment"] = "Request Payment...";
strings_["settings_shield_mining"] = "Shield Mining...";
strings_["settings_merge_to_address"] = "Merge to Address...";
strings_["settings_address_book"] = "Address Book…";
strings_["settings_validate_address"] = "Validate Address…";
strings_["settings_request_payment"] = "Request Payment…";
strings_["settings_shield_mining"] = "Shield Mining…";
strings_["settings_merge_to_address"] = "Merge to Address…";
strings_["settings_clear_ztx"] = "Clear Z-Tx History";
strings_["settings_import_key"] = "Import Private Key...";
strings_["settings_import_viewkey"] = "Import Viewing Key...";
strings_["settings_export_key"] = "Export Key...";
strings_["settings_export_all"] = "Export All...";
strings_["settings_backup"] = "Backup...";
strings_["settings_export_csv"] = "Export CSV...";
strings_["settings_import_key"] = "Import Private Key…";
strings_["settings_import_viewkey"] = "Import Viewing Key…";
strings_["settings_export_key"] = "Export Key…";
strings_["settings_export_all"] = "Export All…";
strings_["settings_backup"] = "Backup…";
strings_["settings_export_csv"] = "Export CSV…";
strings_["settings_encrypt_wallet"] = "Encrypt Wallet";
strings_["settings_change_passphrase"] = "Change Passphrase";
strings_["settings_lock_now"] = "Lock Now";
@@ -655,8 +665,8 @@ void I18n::loadBuiltinEnglish()
strings_["wiz_pin_confirm"] = "Confirm PIN:";
strings_["wiz_pin_invalid"] = "PIN must be 4-8 digits";
strings_["wiz_pin_mismatch"] = "PINs do not match";
strings_["settings_data_dir"] = "Data Dir:";
strings_["settings_wallet_size_label"] = "Wallet Size:";
strings_["settings_data_dir"] = "Data Dir";
strings_["settings_wallet_size_label"] = "Wallet Size";
strings_["settings_debug_changed"] = "Debug categories changed \xe2\x80\x94 restart daemon to apply";
strings_["settings_auto_detected"] = "Auto-detected from DRAGONX.conf";
strings_["settings_visual_effects"] = "Visual Effects";
@@ -670,7 +680,7 @@ void I18n::loadBuiltinEnglish()
strings_["settings_wallet_info"] = "Wallet Info";
strings_["settings_block_explorer_urls"] = "Block Explorer URLs";
strings_["settings_configure_explorer"] = "Configure external block explorer links";
strings_["settings_auto_lock"] = "AUTO-LOCK";
strings_["settings_auto_lock"] = "Auto-lock";
strings_["timeout_off"] = "Off";
strings_["timeout_1min"] = "1 min";
strings_["timeout_5min"] = "5 min";
@@ -700,9 +710,9 @@ void I18n::loadBuiltinEnglish()
strings_["tt_scanline"] = "CRT scanline effect in console";
strings_["tt_theme_effects"] = "Shimmer, glow, hue-cycling per theme";
strings_["tt_animate_avatars"] = "Play animated (GIF / WebP) contact avatars; off shows the first frame only";
strings_["tt_blur"] = "Blur amount (0%% = off, 100%% = maximum)";
strings_["tt_noise"] = "Grain texture intensity (0%% = off, 100%% = maximum)";
strings_["tt_ui_opacity"] = "Card and sidebar opacity (100%% = fully opaque, lower = more see-through)";
strings_["tt_blur"] = "Blur amount (0% = off, 100% = maximum)";
strings_["tt_noise"] = "Grain texture intensity (0% = off, 100% = maximum)";
strings_["tt_ui_opacity"] = "Card and sidebar opacity (100% = fully opaque, lower = more see-through)";
strings_["tt_window_opacity"] = "Background opacity (lower = desktop visible through window)";
strings_["tt_font_scale"] = "Scale all text and UI (1.0x = default, up to 1.5x). Hotkey: Alt + Scroll Wheel";
strings_["tt_custom_theme"] = "Custom theme active";
@@ -805,12 +815,14 @@ void I18n::loadBuiltinEnglish()
strings_["rescan_detecting"] = "Checking which blocks your node has on disk…";
strings_["rescan_bootstrapped_msg"] = "Your node was bootstrapped, so blocks below the snapshot aren't on disk and a rescan from genesis would fail. Rescan from a height your snapshot includes to reconcile your wallet's spent balance. Your wallet.dat and chain data are not deleted.";
strings_["rescan_from_height"] = "Rescan from block height:";
strings_["repair_wallet"] = "Repair Wallet";
strings_["repair_wallet"] = "Repair";
strings_["tt_repair_wallet"] = "Wipe and rebuild the wallet's transaction records from the blockchain (fixes notes that fail to send after a rescan)";
strings_["confirm_repair_wallet_title"] = "Repair Wallet";
strings_["confirm_repair_wallet_msg"] = "This restarts the daemon with -zapwallettxes=2: it deletes all of the wallet's transaction and note records, then rebuilds them from the blockchain. Use this when transactions fail to build (\"Invalid sapling spend proof\" / \"shielded requirements not met\") even after a full rescan. It takes a long time and the wallet stays offline until it finishes.";
strings_["confirm_repair_wallet_safe"] = "Your keys, addresses and balance are preserved — only the cached transaction records are rebuilt.";
strings_["daemon_binary"] = "Daemon binary";
strings_["daemon_updates_label"] = "UPDATES";
strings_["daemon_maintenance_label"] = "MAINTENANCE";
strings_["daemon_installed"] = "Installed";
strings_["daemon_bundled"] = "Bundled";
strings_["daemon_not_installed"] = "not installed";
@@ -818,6 +830,10 @@ void I18n::loadBuiltinEnglish()
strings_["daemon_status_match"] = "Installed binary matches the bundled version.";
strings_["daemon_status_differ"] = "Installed binary differs from the bundled version.";
strings_["daemon_status_missing"] = "No daemon installed — install the bundled version.";
// Compact status shown right-aligned on the DAEMON BINARY heading row.
strings_["daemon_status_ok"] = "Up to date";
strings_["daemon_status_diff"] = "Version differs";
strings_["daemon_status_none"] = "Not installed";
strings_["daemon_install_bundled"] = "Install bundled";
strings_["tt_daemon_install_bundled"] = "Stop the node, overwrite the installed dragonxd with the version bundled in this wallet build, then restart";
strings_["confirm_reinstall_daemon_title"] = "Install Bundled Daemon";
@@ -1500,6 +1516,7 @@ void I18n::loadBuiltinEnglish()
strings_["about_chain"] = "Chain:";
strings_["about_connections"] = "Connections:";
strings_["about_credits"] = "Credits";
strings_["about_source"] = "Source";
strings_["about_daemon"] = "Daemon:";
strings_["about_debug"] = "Debug";
strings_["about_edition"] = "ImGui Edition";