feat(ux): wizard handoff note, daemon restart reminder, settings polish (code)

The code changes intended with the settings_window removal in a90aa29 (a bad
git-add pathspec aborted staging them there, leaving that commit with only the
file deletions and CMakeLists still referencing them):
- First-run handoff info toast at the completion point.
- Daemon-updater "restart pending" reminder (Settings → Daemon Binary) + the
  Done screen's warning-level "restart required" headline.
- Debug restart-note raised to a readable color with the restart prompt beneath
  it; DEBUG OPTIONS header shows a lock + explanatory caption (auth gate
  unchanged); a few hardcoded literals → material tokens / dpiScale.
- Drop the legacy settings_window from the build (CMakeLists + the dev-only
  references) to match the file deletions.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-10-18 23:36:15 -05:00
co-authored by Claude Opus 4.8
parent a43ac0156f
commit 097b2ea014
8 changed files with 105 additions and 38 deletions
-2
View File
@@ -553,7 +553,6 @@ set(APP_SOURCES
src/ui/windows/console_selection_controller.cpp src/ui/windows/console_selection_controller.cpp
src/ui/windows/console_tab_helpers.cpp src/ui/windows/console_tab_helpers.cpp
src/ui/windows/console_text_layout.cpp src/ui/windows/console_text_layout.cpp
src/ui/windows/settings_window.cpp
src/ui/pages/settings_page.cpp src/ui/pages/settings_page.cpp
src/ui/windows/about_dialog.cpp src/ui/windows/about_dialog.cpp
src/ui/windows/faq_dialog.cpp src/ui/windows/faq_dialog.cpp
@@ -704,7 +703,6 @@ set(APP_HEADERS
src/ui/windows/console_selection_controller.h src/ui/windows/console_selection_controller.h
src/ui/windows/console_tab.h src/ui/windows/console_tab.h
src/ui/windows/console_tab_helpers.h src/ui/windows/console_tab_helpers.h
src/ui/windows/settings_window.h
src/ui/windows/about_dialog.h src/ui/windows/about_dialog.h
src/ui/windows/faq_dialog.h src/ui/windows/faq_dialog.h
src/ui/windows/faq_content.h src/ui/windows/faq_content.h
+7 -4
View File
@@ -36,7 +36,6 @@
#include "ui/windows/explorer_tab.h" #include "ui/windows/explorer_tab.h"
#include "ui/windows/seed_display.h" #include "ui/windows/seed_display.h"
#include "ui/windows/market_tab.h" #include "ui/windows/market_tab.h"
#include "ui/windows/settings_window.h"
#include "ui/windows/about_dialog.h" #include "ui/windows/about_dialog.h"
#include "ui/windows/faq_dialog.h" #include "ui/windows/faq_dialog.h"
#include "embedded/IconsMaterialDesign.h" #include "embedded/IconsMaterialDesign.h"
@@ -1963,6 +1962,12 @@ void App::render()
} }
settings_->setWizardCompleted(true); settings_->setWizardCompleted(true);
settings_->save(); settings_->save();
// Soften the abrupt handoff: tell the user the node is starting + will keep syncing in the
// background, instead of dropping them onto a busy/blank main window with no explanation.
if (!state_.connected)
ui::Notifications::instance().info(
settings_->getEncryptionPending() ? TR("wiz_handoff_encrypting") : TR("wiz_handoff_starting"),
10.0f);
} }
// Process deferred encryption from wizard (runs in background) // Process deferred encryption from wizard (runs in background)
@@ -2528,9 +2533,6 @@ void App::render()
// content area, status bar, and every other widget in this window. // content area, status bar, and every other widget in this window.
// ==================================================================== // ====================================================================
PERF_BEGIN(_perfDialogs); PERF_BEGIN(_perfDialogs);
if (show_settings_) {
ui::RenderSettingsWindow(this, &show_settings_);
}
if (show_about_) { if (show_about_) {
ui::RenderAboutDialog(this, &show_about_); ui::RenderAboutDialog(this, &show_about_);
@@ -8182,6 +8184,7 @@ void App::restartDaemon()
isUsingEmbeddedDaemon(), daemon_controller_ != nullptr, isEmbeddedDaemonRunning(), daemon_restarting_.load()); isUsingEmbeddedDaemon(), daemon_controller_ != nullptr, isEmbeddedDaemonRunning(), daemon_restarting_.load());
if (!decision.allowed) return; if (!decision.allowed) return;
if (refuseIfNodeBusy()) return; if (refuseIfNodeBusy()) return;
daemon_restart_pending_.store(false); // a restart is actually happening — clear the reminder
daemon_restarting_ = true; daemon_restarting_ = true;
daemon_controller_->prepareLifecycleOperation(decision, settings_.get()); daemon_controller_->prepareLifecycleOperation(decision, settings_.get());
+4 -1
View File
@@ -769,6 +769,9 @@ public:
* Shows "Restarting daemon..." in the loading overlay while the daemon cycles. * Shows "Restarting daemon..." in the loading overlay while the daemon cycles.
*/ */
void restartDaemon(); void restartDaemon();
// A newer daemon binary is installed but the running node is still the old one until a restart.
void markDaemonRestartPending() { daemon_restart_pending_.store(true); }
bool daemonRestartPending() const { return daemon_restart_pending_.load(); }
// Switch the active wallet: persist the new -wallet=<name>, stop the node, restart on it // Switch the active wallet: persist the new -wallet=<name>, stop the node, restart on it
// (rescan only if it was never synced in this datadir). Per-wallet data follows automatically // (rescan only if it was never synced in this datadir). Per-wallet data follows automatically
// via the identity-scoped caches (P1). No-op if that wallet is already active. // via the identity-scoped caches (P1). No-op if that wallet is already active.
@@ -1311,6 +1314,7 @@ private:
// Daemon restart (e.g. after changing debug log categories) // Daemon restart (e.g. after changing debug log categories)
std::atomic<bool> daemon_restarting_{false}; std::atomic<bool> daemon_restarting_{false};
std::atomic<bool> daemon_restart_pending_{false}; // updated binary installed, awaiting a restart
// Wallet-switch failure recovery: the switch worker sets wallet_switch_failed_ when the new // Wallet-switch failure recovery: the switch worker sets wallet_switch_failed_ when the new
// wallet's daemon won't start/stay up; the main loop then reverts active_wallet_file to // wallet's daemon won't start/stay up; the main loop then reverts active_wallet_file to
@@ -1401,7 +1405,6 @@ private:
// UI State // UI State
bool quit_requested_ = false; bool quit_requested_ = false;
bool show_demo_window_ = false; bool show_demo_window_ = false;
bool show_settings_ = false;
bool show_about_ = false; bool show_about_ = false;
bool show_faq_ = false; bool show_faq_ = false;
bool show_import_key_ = false; bool show_import_key_ = false;
-2
View File
@@ -570,8 +570,6 @@ void App::buildSweepCatalog()
[](App& a) { ui::ContactsSweepCloseDialog(); restoreSweepContacts(a); }); [](App& a) { ui::ContactsSweepCloseDialog(); restoreSweepContacts(a); });
add("modal-about", ui::NavPage::Overview, add("modal-about", ui::NavPage::Overview,
[](App& a) { a.show_about_ = true; }, [](App& a) { a.show_about_ = false; }); [](App& a) { a.show_about_ = true; }, [](App& a) { a.show_about_ = false; });
add("modal-settings", ui::NavPage::Settings,
[](App& a) { a.show_settings_ = true; }, [](App& a) { a.show_settings_ = false; });
// Seed-backup: pre-set fetch + a demo phrase so it renders the word grid without any RPC. // Seed-backup: pre-set fetch + a demo phrase so it renders the word grid without any RPC.
add("modal-seed-backup", ui::NavPage::Overview, add("modal-seed-backup", ui::NavPage::Overview,
+2 -1
View File
@@ -1456,7 +1456,8 @@ void App::renderFirstRunWizard() {
wizard_phase_ = WizardPhase::Done; wizard_phase_ = WizardPhase::Done;
settings_->setWizardCompleted(true); settings_->setWizardCompleted(true);
settings_->save(); settings_->save();
ui::Notifications::instance().info(TR("wiz_encrypt_bg")); // The handoff toast is now emitted once at the completion point in app.cpp
// (wiz_handoff_encrypting), so don't also toast here.
} }
ImGui::EndDisabled(); ImGui::EndDisabled();
ImGui::PopStyleVar(); ImGui::PopStyleVar();
+63 -27
View File
@@ -904,7 +904,7 @@ void RenderSettingsPage(App* app) {
float cellGap = Layout::spacingLg(); float cellGap = Layout::spacingLg();
bool twoCol = contentW >= 460.0f * dp; // drop to a single column when too narrow (high font scale) bool twoCol = contentW >= 460.0f * dp; // drop to a single column when too narrow (high font scale)
int cols = twoCol ? 2 : 1; int cols = twoCol ? 2 : 1;
float colW = std::max(160.0f, twoCol ? (contentW - cellGap) * 0.5f : contentW); float colW = std::max(160.0f * dp, twoCol ? (contentW - cellGap) * 0.5f : contentW);
float baseX = ImGui::GetCursorScreenPos().x; float baseX = ImGui::GetCursorScreenPos().x;
const char* cellLabels[4] = { TR("theme"), TR("balance_layout"), TR("language"), TR("clock_format") }; const char* cellLabels[4] = { TR("theme"), TR("balance_layout"), TR("language"), TR("clock_format") };
@@ -1346,7 +1346,7 @@ void RenderSettingsPage(App* app) {
ImGui::SetCursorScreenPos(ImVec2(cx, last + gp)); ImGui::SetCursorScreenPos(ImVec2(cx, last + gp));
ImGui::PushTextWrapPos((cx + cw) - ImGui::GetWindowPos().x); // wrap to the card content width ImGui::PushTextWrapPos((cx + cw) - ImGui::GetWindowPos().x); // wrap to the card content width
if (app->daemonAutoShieldActive()) { if (app->daemonAutoShieldActive()) {
ImGui::TextColored(ImVec4(0.40f, 0.78f, 0.40f, 1.0f), "%s", TR("autoshield_by_node")); ImGui::TextColored(material::SuccessVec4(), "%s", TR("autoshield_by_node"));
if (!app->daemonAutoShieldAddress().empty()) if (!app->daemonAutoShieldAddress().empty())
ImGui::TextDisabled(" %s", app->daemonAutoShieldAddress().c_str()); ImGui::TextDisabled(" %s", app->daemonAutoShieldAddress().c_str());
} else { } else {
@@ -2595,6 +2595,21 @@ void RenderSettingsPage(App* app) {
if (ui::DaemonUpdateDialog::consumeInstalled()) if (ui::DaemonUpdateDialog::consumeInstalled())
s_settingsState.daemon_info_loaded = false; s_settingsState.daemon_info_loaded = false;
// Persistent reminder: a newer daemon binary is installed but the running node is still
// the old one until a restart (easy to miss on the updater's Done screen).
if (app->daemonRestartPending()) {
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
ImGui::PushTextWrapPos(ImGui::GetCursorPosX() + contentW);
Type().textColored(TypeStyle::Caption, material::Warning(), TR("daemon_restart_pending"));
ImGui::PopTextWrapPos();
if (app->isUsingEmbeddedDaemon()) {
if (material::ActionButton("##drestartpend", TR("daemon_update_restart_now"),
ICON_MD_RESTART_ALT, material::ActionTier::Primary))
app->restartDaemon();
if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_daemon_restart_pending"));
}
}
// --- UPDATES --- // --- UPDATES ---
ImGui::Dummy(ImVec2(0, Layout::spacingSm())); ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("daemon_updates_label")); Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("daemon_updates_label"));
@@ -3001,14 +3016,23 @@ void RenderSettingsPage(App* app) {
s_settingsState.debug_gate_verifying = false; s_settingsState.debug_gate_verifying = false;
} }
} }
if (ImGui::IsItemHovered()) material::Tooltip("%s", s_settingsState.debug_expanded ? TR("tt_debug_collapse") : TR("tt_debug_expand")); if (ImGui::IsItemHovered()) material::Tooltip("%s", s_settingsState.debug_expanded ? TR("tt_debug_collapse")
: (s_settingsState.debug_gate_passed ? TR("tt_debug_expand") : TR("tt_debug_expand_gated")));
ImGui::PopStyleColor(3); ImGui::PopStyleColor(3);
// Draw overline label + arrow on top of the invisible button // Draw overline label + arrow on top of the invisible button
{ {
ImFont* ovFont = Type().overline(); ImFont* ovFont = Type().overline();
float textY = headerPos.y + (ImGui::GetFrameHeight() - ovFont->LegacySize) * 0.5f; float textY = headerPos.y + (ImGui::GetFrameHeight() - ovFont->LegacySize) * 0.5f;
dl->AddText(ovFont, ovFont->LegacySize, ImVec2(headerPos.x, textY), OnSurfaceMedium(), TR("debug_logging")); float labelX = headerPos.x;
// Show a lock beside the label while the session gate hasn't been passed, so it's clear the
// section is gated rather than just a bare collapsible header.
if (!s_settingsState.debug_gate_passed) {
ImFont* lockFont = Type().iconSmall(); if (!lockFont) lockFont = ovFont;
dl->AddText(lockFont, lockFont->LegacySize, ImVec2(labelX, textY), OnSurfaceMedium(), ICON_MD_LOCK);
labelX += lockFont->CalcTextSizeA(lockFont->LegacySize, FLT_MAX, 0, ICON_MD_LOCK).x + Layout::spacingXs();
}
dl->AddText(ovFont, ovFont->LegacySize, ImVec2(labelX, textY), OnSurfaceMedium(), TR("debug_logging"));
// Use the icon font for the expand/collapse arrow // Use the icon font for the expand/collapse arrow
ImFont* iconFont = Type().iconSmall(); ImFont* iconFont = Type().iconSmall();
if (!iconFont) iconFont = ovFont; if (!iconFont) iconFont = ovFont;
@@ -3016,6 +3040,14 @@ void RenderSettingsPage(App* app) {
dl->AddText(iconFont, iconFont->LegacySize, ImVec2(headerPos.x + availWidth - arrowW - pad, textY), OnSurfaceMedium(), arrow); dl->AddText(iconFont, iconFont->LegacySize, ImVec2(headerPos.x + availWidth - arrowW - pad, textY), OnSurfaceMedium(), arrow);
} }
// One-line explanation under the collapsed header (what it is + that it's gated).
if (!s_settingsState.debug_expanded) {
Type().textColored(TypeStyle::Caption, OnSurfaceMedium(),
s_settingsState.debug_gate_passed ? TR("debug_options_caption_unlocked")
: TR("debug_options_caption_gated"));
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
}
if (s_settingsState.debug_expanded) { if (s_settingsState.debug_expanded) {
ImGui::Dummy(ImVec2(0, Layout::spacingXs())); ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
@@ -3059,9 +3091,33 @@ void RenderSettingsPage(App* app) {
ImGui::Dummy(ImVec2(0, Layout::spacingSm())); ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
#endif #endif
ImGui::TextColored(ImVec4(1,1,1,0.5f), "%s", TR("settings_debug_select")); ImGui::PushStyleColor(ImGuiCol_Text, material::OnSurfaceMedium());
ImGui::TextColored(ImVec4(1,1,1,0.35f), "%s", TR("settings_debug_restart_note")); ImGui::TextUnformatted(TR("settings_debug_select"));
ImGui::TextWrapped("%s", TR("settings_debug_restart_note")); // was near-invisible 35% white
ImGui::PopStyleColor();
ImGui::Dummy(ImVec2(0, Layout::spacingSm())); ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
// Restart prompt moved directly under the note so the required action is prominent (appears the
// frame after a category toggles, which sets debug_cats_dirty while rendering the grid below).
if (s_settingsState.debug_cats_dirty && app->supportsFullNodeLifecycleActions()) {
ImGui::PushStyleColor(ImGuiCol_Text, material::Warning());
ImFont* iconFont = Type().iconSmall();
if (iconFont) {
ImGui::PushFont(iconFont);
ImGui::AlignTextToFramePadding();
ImGui::TextUnformatted(ICON_MD_INFO);
ImGui::PopFont();
ImGui::SameLine(0, Layout::spacingXs());
}
ImGui::AlignTextToFramePadding();
ImGui::TextUnformatted(TR("settings_debug_changed"));
ImGui::PopStyleColor();
ImGui::SameLine();
if (TactileButton(TR("settings_restart_daemon"), ImVec2(0, 0), S.resolveFont("button"))) {
s_settingsState.confirm_restart_daemon = true;
}
if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_restart_daemon"));
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
}
// The 22 dragonxd debug categories // The 22 dragonxd debug categories
static const char* debugCats[] = { static const char* debugCats[] = {
@@ -3119,27 +3175,7 @@ void RenderSettingsPage(App* app) {
} }
ImGui::Dummy(ImVec2(0, Layout::spacingSm())); ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
// (The "restart daemon" prompt was moved up to directly under the restart note above.)
// "Restart daemon" button — only active when categories changed
if (s_settingsState.debug_cats_dirty && app->supportsFullNodeLifecycleActions()) {
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 218, 0, 255));
ImFont* iconFont = Type().iconSmall();
if (iconFont) {
ImGui::PushFont(iconFont);
ImGui::AlignTextToFramePadding();
ImGui::TextUnformatted(ICON_MD_INFO);
ImGui::PopFont();
ImGui::SameLine(0, Layout::spacingXs());
}
ImGui::AlignTextToFramePadding();
ImGui::TextUnformatted(TR("settings_debug_changed"));
ImGui::PopStyleColor();
ImGui::SameLine();
if (TactileButton(TR("settings_restart_daemon"), ImVec2(0, 0), S.resolveFont("button"))) {
s_settingsState.confirm_restart_daemon = true;
}
if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("tt_restart_daemon"));
}
} }
// ---- Debug-options gate: confirmation + warning (+ re-auth if a PIN/passphrase is set) ---- // ---- Debug-options gate: confirmation + warning (+ re-auth if a PIN/passphrase is set) ----
+11 -1
View File
@@ -445,11 +445,20 @@ private:
static void renderDone(float, const Progress& p) { static void renderDone(float, const Progress& p) {
using namespace material; using namespace material;
if (!s_notified) { s_installed_flag = true; s_notified = true; } if (!s_notified) {
s_installed_flag = true;
s_notified = true;
// Record a persistent "restart pending" reminder so it isn't lost if the user clicks Later.
if (s_app) s_app->markDaemonRestartPending();
}
Type().textColored(TypeStyle::Subtitle2, Success(), TR("daemon_update_installed_ok")); Type().textColored(TypeStyle::Subtitle2, Success(), TR("daemon_update_installed_ok"));
ImGui::Spacing(); ImGui::Spacing();
ImGui::Text("%s %s", TR("daemon_update_version"), p.latest_tag.c_str()); ImGui::Text("%s %s", TR("daemon_update_version"), p.latest_tag.c_str());
ImGui::Spacing(); ImGui::Spacing();
// Lead with a warning-level headline that a restart is required, then the detail note.
ImGui::PushTextWrapPos(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x);
Type().textColored(TypeStyle::Subtitle2, Warning(), TR("daemon_update_restart_needed"));
ImGui::PopTextWrapPos();
Type().textColored(TypeStyle::Caption, OnSurfaceMedium(), TR("daemon_update_restart_note")); Type().textColored(TypeStyle::Caption, OnSurfaceMedium(), TR("daemon_update_restart_note"));
ImGui::Spacing(); ImGui::Spacing();
if (s_app->isUsingEmbeddedDaemon()) { if (s_app->isUsingEmbeddedDaemon()) {
@@ -459,6 +468,7 @@ private:
} }
ImGui::Spacing(); ImGui::Spacing();
if (TactileButton(TR("daemon_update_later"), ImVec2(fullW(), 0))) s_open = false; if (TactileButton(TR("daemon_update_later"), ImVec2(fullW(), 0))) s_open = false;
Type().textColored(TypeStyle::Caption, OnSurfaceMedium(), TR("daemon_update_later_hint"));
} else { } else {
if (TactileButton(TR("close"), ImVec2(fullW(), 0))) s_open = false; if (TactileButton(TR("close"), ImVec2(fullW(), 0))) s_open = false;
} }
+18
View File
@@ -315,6 +315,10 @@ void I18n::loadBuiltinEnglish()
strings_["contacts_message_needs_z"] = "Chat needs a shielded (z) address — this contact only has a transparent one"; strings_["contacts_message_needs_z"] = "Chat needs a shielded (z) address — this contact only has a transparent one";
strings_["chat_copy_address_tip"] = "Click to copy address"; strings_["chat_copy_address_tip"] = "Click to copy address";
strings_["chat_verify_key"] = "Identity key \xE2\x80\x94 compare to verify"; strings_["chat_verify_key"] = "Identity key \xE2\x80\x94 compare to verify";
strings_["chat_verify_tip"] = "End-to-end encrypted to this contact's identity key. Click to view the key fingerprint and compare it with your contact over a separate channel (call or in person) to be sure no one is impersonating them.";
strings_["chat_verify_title"] = "Verify contact key";
strings_["chat_verify_body"] = "Messages in this chat are encrypted to the identity key below, which was learned from this contact's first message. Ask your contact to read you their key fingerprint over a separate channel (a call or in person). If it matches exactly, you are talking to the right person; if not, do not trust this conversation.";
strings_["chat_verify_copy"] = "Copy key";
strings_["chat_awaiting_key"] = "Waiting for reply"; strings_["chat_awaiting_key"] = "Waiting for reply";
strings_["chat_rename"] = "Rename contact"; strings_["chat_rename"] = "Rename contact";
strings_["chat_rename_hint"] = "Contact name"; strings_["chat_rename_hint"] = "Contact name";
@@ -773,6 +777,8 @@ void I18n::loadBuiltinEnglish()
strings_["wiz_pass_spaces"] = "Passphrase has leading/trailing spaces \xe2\x80\x94 remove them"; strings_["wiz_pass_spaces"] = "Passphrase has leading/trailing spaces \xe2\x80\x94 remove them";
strings_["wiz_encrypt_continue"] = "Encrypt & Continue"; strings_["wiz_encrypt_continue"] = "Encrypt & Continue";
strings_["wiz_encrypt_bg"] = "Encryption will complete in the background"; strings_["wiz_encrypt_bg"] = "Encryption will complete in the background";
strings_["wiz_handoff_starting"] = "Setting up your wallet. The node is starting and will keep syncing in the background. You can start exploring now, but balances may take a while to appear.";
strings_["wiz_handoff_encrypting"] = "Setting up your wallet. The node is starting, and encryption will finish in the background once it connects. Balances may take a while to appear.";
strings_["wiz_skip_confirm"] = "Continue WITHOUT encryption? Keys will be stored unencrypted \xe2\x80\x94 click Skip again to confirm."; strings_["wiz_skip_confirm"] = "Continue WITHOUT encryption? Keys will be stored unencrypted \xe2\x80\x94 click Skip again to confirm.";
strings_["wiz_pin_optional"] = "Quick-Unlock PIN (optional)"; strings_["wiz_pin_optional"] = "Quick-Unlock PIN (optional)";
strings_["wiz_pin_label"] = "PIN (4-8 digits):"; strings_["wiz_pin_label"] = "PIN (4-8 digits):";
@@ -1058,6 +1064,9 @@ void I18n::loadBuiltinEnglish()
strings_["tt_reset_settings"] = "Revert this page to the last saved settings (undo unsaved changes)"; strings_["tt_reset_settings"] = "Revert this page to the last saved settings (undo unsaved changes)";
strings_["tt_debug_collapse"] = "Collapse debug logging options"; strings_["tt_debug_collapse"] = "Collapse debug logging options";
strings_["tt_debug_expand"] = "Expand debug logging options"; strings_["tt_debug_expand"] = "Expand debug logging options";
strings_["debug_options_caption_gated"] = "Diagnostics and node tuning for troubleshooting. Locked: opening requires confirmation (and your PIN or passphrase if set).";
strings_["debug_options_caption_unlocked"] = "Diagnostics and node tuning for troubleshooting. Unlocked for this session.";
strings_["tt_debug_expand_gated"] = "Locked: confirm (and enter your PIN or passphrase if set) to show debug options";
strings_["tt_restart_daemon"] = "Restart the daemon to apply debug logging changes"; strings_["tt_restart_daemon"] = "Restart the daemon to apply debug logging changes";
// Settings: additional tooltips (keys/data row) // Settings: additional tooltips (keys/data row)
@@ -1991,6 +2000,9 @@ void I18n::loadBuiltinEnglish()
strings_["toast_stop_mining_failed"] = "Couldn't stop mining: "; strings_["toast_stop_mining_failed"] = "Couldn't stop mining: ";
strings_["toast_pool_start_failed"] = "Couldn't start the pool miner: "; strings_["toast_pool_start_failed"] = "Couldn't start the pool miner: ";
strings_["toast_ban_failed"] = "Couldn't ban the peer: "; strings_["toast_ban_failed"] = "Couldn't ban the peer: ";
strings_["toast_peer_banned"] = "Peer banned.";
strings_["toast_peer_unbanned"] = "Peer unbanned.";
strings_["toast_bans_cleared"] = "All bans cleared.";
strings_["toast_unban_failed"] = "Couldn't unban the peer: "; strings_["toast_unban_failed"] = "Couldn't unban the peer: ";
strings_["toast_clear_bans_failed"] = "Couldn't clear bans: "; strings_["toast_clear_bans_failed"] = "Couldn't clear bans: ";
strings_["toast_key_export_failed"] = "Key export failed: "; strings_["toast_key_export_failed"] = "Key export failed: ";
@@ -2482,6 +2494,10 @@ void I18n::loadBuiltinEnglish()
strings_["daemon_update_installed_ok"] = "Node installed"; strings_["daemon_update_installed_ok"] = "Node installed";
strings_["daemon_update_restart_note"] = "Restart the daemon to start running the new version."; strings_["daemon_update_restart_note"] = "Restart the daemon to start running the new version.";
strings_["daemon_update_restart_now"] = "Restart daemon now"; strings_["daemon_update_restart_now"] = "Restart daemon now";
strings_["daemon_update_restart_needed"] = "Restart required to use the new version";
strings_["daemon_update_later_hint"] = "A reminder stays in Settings until you restart the daemon.";
strings_["daemon_restart_pending"] = "A new daemon version is installed but not running yet. Restart the daemon to use it.";
strings_["tt_daemon_restart_pending"] = "Restarts the embedded daemon so the newly installed version takes effect.";
strings_["daemon_update_later"] = "Later"; strings_["daemon_update_later"] = "Later";
strings_["daemon_update_failed"] = "Update failed"; strings_["daemon_update_failed"] = "Update failed";
strings_["daemon_update_unknown_error"] = "Unknown error."; strings_["daemon_update_unknown_error"] = "Unknown error.";
@@ -2538,6 +2554,8 @@ void I18n::loadBuiltinEnglish()
strings_["peers_blocks"] = "Blocks"; strings_["peers_blocks"] = "Blocks";
strings_["peers_blocks_left"] = "%d blocks left"; strings_["peers_blocks_left"] = "%d blocks left";
strings_["peers_clear_all_bans"] = "Clear All Bans"; strings_["peers_clear_all_bans"] = "Clear All Bans";
strings_["peers_clear_bans_confirm"] = "Clear all %d bans?";
strings_["peers_clear_bans_confirm_yes"] = "Clear all";
strings_["peers_click_copy"] = "Click to copy"; strings_["peers_click_copy"] = "Click to copy";
strings_["peers_connected"] = "Connected"; strings_["peers_connected"] = "Connected";
strings_["peers_connected_count"] = "Connected: %d"; strings_["peers_connected_count"] = "Connected: %d";