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

@@ -215,11 +215,15 @@ private:
ImGui::Spacing();
ImGui::Spacing();
// Cancel button
// Cancel button — once clicked, disable it so the request is visibly
// acknowledged (and can't be re-fired) until the worker actually stops.
float btnW = 100.0f * dp;
ImGui::BeginDisabled(s_cancelling);
if (TactileButton(TR("cancel"), ImVec2(btnW, 0))) {
s_cancelling = true;
s_bootstrap->cancel();
}
ImGui::EndDisabled();
// Check completion
if (s_bootstrap->isDone()) {
@@ -234,6 +238,7 @@ private:
s_state = State::Failed;
}
s_bootstrap.reset();
s_cancelling = false;
}
}
@@ -298,6 +303,7 @@ private:
s_bootstrap->start(dataDir, url);
s_state = State::Downloading;
s_errorMsg.clear();
s_cancelling = false;
}
static inline bool s_open = false;
@@ -305,6 +311,7 @@ private:
static inline State s_state = State::Confirm;
static inline std::unique_ptr<util::Bootstrap> s_bootstrap;
static inline bool s_wasDaemonRunning = false;
static inline bool s_cancelling = false; // cancel requested; disables the Cancel button until the worker stops
static inline std::string s_errorMsg;
};