fix(ui): tidy Node maintenance layout + widen the Daemon binary panel

- Delete Blockchain and Repair Wallet now sit on one line, paired with the same
  uniform button width as Test Connection / Rescan Blockchain (a clean 2-column grid;
  all maintenance buttons share one rowBtnW sized across the four labels).
- The Daemon binary panel lays Installed and Bundled side by side across the node
  column (version + size·date | version + size) instead of stacked narrow lines, with
  the status line spanning underneath and Install bundled / Refresh paired below.
- Shorten the install button label to "Install bundled" so it fits the shared width;
  the tooltip still explains the full action. Date shown as YYYY-MM-DD.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-18 21:21:06 -05:00
parent b2e104358d
commit 6544c10ac1
2 changed files with 38 additions and 25 deletions

View File

@@ -2012,7 +2012,8 @@ void RenderSettingsPage(App* app) {
float nodeBtnW;
{
if (btnFont) ImGui::PushFont(btnFont);
nodeBtnW = rowBtnW({TR("test_connection"), TR("rescan")});
nodeBtnW = rowBtnW({TR("test_connection"), TR("rescan"),
TR("delete_blockchain"), TR("repair_wallet")});
if (btnFont) ImGui::PopFont(/* btnFont */);
}
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
@@ -2045,20 +2046,16 @@ void RenderSettingsPage(App* app) {
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) ImGui::SetTooltip("%s", TR("tt_rescan"));
ImGui::EndDisabled();
// Delete blockchain button (always available when using embedded daemon)
// Row 2: Delete blockchain | Repair wallet (embedded daemon only) — paired on
// one line with the same width/style as the Test connection | Rescan row above.
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y + Layout::spacingSm()));
ImGui::BeginDisabled(!app->isUsingEmbeddedDaemon());
if (TactileButton(TR("delete_blockchain"), ImVec2(0, 0), btnFont)) {
if (TactileButton(TR("delete_blockchain"), ImVec2(nodeBtnW, 0), btnFont)) {
s_settingsState.confirm_delete_blockchain = true;
}
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) ImGui::SetTooltip("%s", TR("tt_delete_blockchain"));
ImGui::EndDisabled();
// Repair wallet (-zapwallettxes=2): wipe & rebuild wallet tx/note records from
// the chain (keys kept). Fixes notes that fail to spend after a rescan.
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y + Layout::spacingSm()));
ImGui::BeginDisabled(!app->isUsingEmbeddedDaemon());
if (TactileButton(TR("repair_wallet"), ImVec2(0, 0), btnFont)) {
ImGui::SameLine(0, Layout::spacingMd());
if (TactileButton(TR("repair_wallet"), ImVec2(nodeBtnW, 0), btnFont)) {
s_settingsState.confirm_repair_wallet = true;
}
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) ImGui::SetTooltip("%s", TR("tt_repair_wallet"));
@@ -2086,7 +2083,7 @@ void RenderSettingsPage(App* app) {
localtime_r(&t, &tmv);
#endif
char buf[32];
std::strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M", &tmv);
std::strftime(buf, sizeof(buf), "%Y-%m-%d", &tmv);
return std::string(buf);
};
@@ -2094,25 +2091,40 @@ void RenderSettingsPage(App* app) {
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("daemon_binary"));
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
// Installed | Bundled side by side, spanning the node column's width.
const float dbStartY = ImGui::GetCursorScreenPos().y;
const float dbLineH = ImGui::GetTextLineHeightWithSpacing();
const float dbCol2X = leftX + leftColW * 0.5f;
const ImVec4 dbDim = ImGui::ColorConvertU32ToFloat4(OnSurfaceMedium());
// Column 1 — Installed (version, then size · date)
ImGui::SetCursorScreenPos(ImVec2(leftX, dbStartY));
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("daemon_installed"));
ImGui::SetCursorScreenPos(ImVec2(leftX, dbStartY + dbLineH));
if (inst.exists) {
ImGui::Text("%s %s", TR("daemon_installed"),
inst.version.empty() ? TR("unknown") : inst.version.c_str());
ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(OnSurfaceMedium()), " %s · %s",
ImGui::TextUnformatted(inst.version.empty() ? TR("unknown") : inst.version.c_str());
ImGui::SetCursorScreenPos(ImVec2(leftX, dbStartY + dbLineH * 2));
ImGui::TextColored(dbDim, "%s · %s",
util::Platform::formatFileSize(inst.size).c_str(),
fmtDate(inst.modifiedEpoch).c_str());
} else {
ImGui::Text("%s %s", TR("daemon_installed"), TR("daemon_not_installed"));
ImGui::TextColored(dbDim, "%s", TR("daemon_not_installed"));
}
// Column 2 — Bundled (version, then size)
ImGui::SetCursorScreenPos(ImVec2(dbCol2X, dbStartY));
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("daemon_bundled"));
ImGui::SetCursorScreenPos(ImVec2(dbCol2X, dbStartY + dbLineH));
if (bun.available) {
ImGui::Text("%s %s", TR("daemon_bundled"),
bun.version.empty() ? TR("unknown") : bun.version.c_str());
ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(OnSurfaceMedium()), " %s",
util::Platform::formatFileSize(bun.size).c_str());
ImGui::TextUnformatted(bun.version.empty() ? TR("unknown") : bun.version.c_str());
ImGui::SetCursorScreenPos(ImVec2(dbCol2X, dbStartY + dbLineH * 2));
ImGui::TextColored(dbDim, "%s", util::Platform::formatFileSize(bun.size).c_str());
} else {
ImGui::Text("%s %s", TR("daemon_bundled"), TR("daemon_none_bundled"));
ImGui::TextColored(dbDim, "%s", TR("daemon_none_bundled"));
}
// Status line, below both sub-columns, spanning the node column.
ImGui::SetCursorScreenPos(ImVec2(leftX, dbStartY + dbLineH * 3 + Layout::spacingXs()));
if (bun.available) {
const bool sameSize = inst.exists && inst.size == bun.size;
if (!inst.exists)
@@ -2124,15 +2136,16 @@ void RenderSettingsPage(App* app) {
}
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
// Install bundled daemon | Refresh — paired, same width/style as the rows above.
ImGui::SetCursorScreenPos(ImVec2(leftX, ImGui::GetCursorScreenPos().y));
ImGui::BeginDisabled(!app->isUsingEmbeddedDaemon() || !bun.available);
if (TactileButton(TR("daemon_install_bundled"), ImVec2(0, 0), btnFont)) {
if (TactileButton(TR("daemon_install_bundled"), ImVec2(nodeBtnW, 0), btnFont)) {
s_settingsState.confirm_reinstall_daemon = true;
}
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) ImGui::SetTooltip("%s", TR("tt_daemon_install_bundled"));
ImGui::EndDisabled();
ImGui::SameLine(0, Layout::spacingMd());
if (TactileButton(TR("refresh"), ImVec2(0, 0), btnFont)) {
if (TactileButton(TR("refresh"), ImVec2(nodeBtnW, 0), btnFont)) {
s_settingsState.daemon_info_loaded = false; // recompute next frame
}
}

View File

@@ -424,14 +424,14 @@ void I18n::loadBuiltinEnglish()
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_installed"] = "Installed:";
strings_["daemon_bundled"] = "Bundled:";
strings_["daemon_installed"] = "Installed";
strings_["daemon_bundled"] = "Bundled";
strings_["daemon_not_installed"] = "not installed";
strings_["daemon_none_bundled"] = "none in this build";
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.";
strings_["daemon_install_bundled"] = "Install bundled daemon";
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";
strings_["confirm_reinstall_daemon_msg"] = "This stops the daemon, overwrites the installed dragonxd (and dragonx-cli/dragonx-tx) with the versions bundled in this wallet build, then restarts the node. Use this to recover or update the node binary.";