fix(ui): prevent stuck spinners and double-submits in dialogs

Address a cluster of low-severity robustness nits where an async
callback could be skipped (leaving a spinner spinning forever) or a
synchronous action could be re-fired mid-flight:

- app_network: add catch(...) fallbacks around the importPrivateKey and
  submitZSendMany worker lambdas so a non-std throw can't escape the
  worker and skip the main-thread callback (stuck "Importing…"/"Sending…").
- export_transactions: re-entrancy guard disabling Export while a write
  is in flight.
- bootstrap_download: disable Cancel once clicked so the request is
  visibly acknowledged and can't be re-fired before the worker stops.
- network_tab: give clear "already in list" feedback on a duplicate
  server (keep the inputs, clear only the stale invalid-URL error) and
  disable/relabel Refresh while a probe is in flight.
- key_export: offer Retry from the error branch (falls back to Reveal)
  and guard against an empty/malformed address before dispatching.
- receive_tab: only enter the "generating…" state when a dispatch is
  actually possible (guards a stuck spinner when disconnected).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-05 15:08:43 -05:00
parent 1dbbd44759
commit 41f5548593
6 changed files with 86 additions and 30 deletions

View File

@@ -143,6 +143,15 @@ void KeyExportDialog::render(App* app)
ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.0f, 1.0f), "%s", TR("key_export_fetching"));
} else if (!s_error.empty()) {
ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f), TR("error_format"), s_error.c_str());
// Not a dead-end: clearing the error falls back through to the Reveal button next frame.
ImGui::Spacing();
if (material::StyledButton(TR("retry"), ImVec2(revealBtn.width, 0), S.resolveFont(revealBtn.font))) {
s_error.clear();
}
} else if (s_key.empty() && s_address.length() < 26) {
// Guard against an empty/blatantly-malformed address before dispatching a doomed reveal.
// (Also avoids reading s_address[0] on an empty string in the Reveal branch below.)
ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f), "%s", TR("invalid_address"));
} else if (s_key.empty()) {
// Show button to fetch key
if (material::StyledButton(TR("key_export_reveal"), ImVec2(revealBtn.width, 0), S.resolveFont(revealBtn.font))) {