feat(shield): colour shield/merge status by outcome + spinner; friendly errors
The shield/merge status line was amber only while in-flight and uncoloured for every terminal state (success, failure, uncertain). It now tracks an OpOutcome (display-only) and colours the terminal line Success/Error/Warning accordingly, shows a LoadingDots spinner while the 120s scan runs, and maps the raw daemon error through friendlyRpcError. The actual shield/merge submit logic is unchanged (no "Try again" that could double-submit a fund move). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -41,6 +41,8 @@ static bool s_advanced = false; // Advanced (fee + batch size)
|
||||
static bool s_confirm = false; // inline "confirm before moving funds" phase
|
||||
static bool s_operation_pending = false;
|
||||
static bool s_op_terminal = false; // async op reached success/failed — freeze inputs
|
||||
enum class OpOutcome { None, Success, Failed, Uncertain };
|
||||
static OpOutcome s_op_outcome = OpOutcome::None; // tags the terminal status for colour (display only)
|
||||
static std::string s_operation_id;
|
||||
static std::string s_status_message;
|
||||
static double s_last_poll = 0.0; // live-progress self-poll timer (ImGui::GetTime seconds)
|
||||
@@ -68,6 +70,7 @@ static void resetTransient()
|
||||
++s_poll_session;
|
||||
s_operation_pending = false;
|
||||
s_op_terminal = false;
|
||||
s_op_outcome = OpOutcome::None;
|
||||
s_confirm = false;
|
||||
s_status_message.clear();
|
||||
s_operation_id.clear();
|
||||
@@ -232,6 +235,7 @@ static void submitOperation(App* app)
|
||||
if (!app->worker()) return; // before the pending state, so a missing worker can't wedge it
|
||||
s_operation_pending = true;
|
||||
s_op_terminal = false;
|
||||
s_op_outcome = OpOutcome::None;
|
||||
s_status_message = TR("shield_submitting");
|
||||
s_last_poll = ImGui::GetTime();
|
||||
|
||||
@@ -253,8 +257,9 @@ static void submitOperation(App* app)
|
||||
app->trackOperation(s_operation_id);
|
||||
} else {
|
||||
s_operation_pending = false; s_op_terminal = true;
|
||||
s_status_message = std::string(TR("shield_error_prefix")) + error;
|
||||
Notifications::instance().error(std::string(TR("shield_send_failed")) + error);
|
||||
s_op_outcome = OpOutcome::Failed;
|
||||
s_status_message = std::string(TR("shield_error_prefix")) + util::friendlyRpcError(error);
|
||||
Notifications::instance().error(std::string(TR("shield_send_failed")) + util::friendlyRpcError(error));
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -309,13 +314,15 @@ static void submitOperation(App* app)
|
||||
app->trackOperation(s_operation_id);
|
||||
} else if (uncertain) {
|
||||
s_operation_pending = false; s_op_terminal = true;
|
||||
s_op_outcome = OpOutcome::Uncertain;
|
||||
s_confirm = false; // don't leave Confirm armed for a merge that may still be running
|
||||
s_status_message = TR("merge_submit_uncertain");
|
||||
Notifications::instance().warning(TR("merge_submit_uncertain"));
|
||||
} else {
|
||||
s_operation_pending = false; s_op_terminal = true;
|
||||
s_status_message = std::string(TR("shield_error_prefix")) + error;
|
||||
Notifications::instance().error(std::string(TR("merge_send_failed")) + error);
|
||||
s_op_outcome = OpOutcome::Failed;
|
||||
s_status_message = std::string(TR("shield_error_prefix")) + util::friendlyRpcError(error);
|
||||
Notifications::instance().error(std::string(TR("merge_send_failed")) + util::friendlyRpcError(error));
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -340,6 +347,7 @@ static void submitPerAddress(App* app)
|
||||
s_confirm = false;
|
||||
s_status_message.clear(); // an earlier "Into one" error / uncertain text belongs to that op
|
||||
s_op_terminal = false;
|
||||
s_op_outcome = OpOutcome::None;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -384,6 +392,7 @@ static void pollOperation(App* app)
|
||||
// client popped it with z_getoperationresult). It registers an op before returning its id,
|
||||
// so this is no race. The outcome can't be confirmed; stop polling.
|
||||
s_operation_pending = false; s_op_terminal = true;
|
||||
s_op_outcome = OpOutcome::Uncertain;
|
||||
s_status_message = TR("send_status_unconfirmed");
|
||||
Notifications::instance().warning(TR("send_status_unconfirmed"));
|
||||
return;
|
||||
@@ -393,6 +402,7 @@ static void pollOperation(App* app)
|
||||
? op["status"].get<std::string>() : std::string();
|
||||
if (status == "success") {
|
||||
s_operation_pending = false; s_op_terminal = true;
|
||||
s_op_outcome = OpOutcome::Success;
|
||||
s_status_message = TR("shield_completed");
|
||||
Notifications::instance().success(TR("shield_merge_done"));
|
||||
} else if (status == "failed" || status == "cancelled" || status == "canceled") {
|
||||
@@ -401,6 +411,7 @@ static void pollOperation(App* app)
|
||||
op["error"]["message"].is_string())
|
||||
msg = op["error"]["message"].get<std::string>();
|
||||
s_operation_pending = false; s_op_terminal = true;
|
||||
s_op_outcome = OpOutcome::Failed;
|
||||
s_status_message = std::string(TR("shield_op_failed")) + msg;
|
||||
// No toast: the op is tracked (App::trackOperation) and the shared opid poller raises the
|
||||
// one failure toast, whether this dialog is open or not.
|
||||
@@ -746,10 +757,18 @@ void ShieldDialog::render(App* app)
|
||||
|
||||
// ── Live progress / status ──────────────────────────────────────────────────────────────────
|
||||
if (!s_status_message.empty() && !(isMerge && run.hasRun())) { // the single-op path's message, not the run's
|
||||
if (s_operation_pending && !s_op_terminal)
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.0f, 1.0f), "%s", s_status_message.c_str());
|
||||
const bool inFlight = s_operation_pending && !s_op_terminal;
|
||||
const ImU32 stCol = inFlight ? material::Warning()
|
||||
: s_op_outcome == OpOutcome::Success ? material::Success()
|
||||
: s_op_outcome == OpOutcome::Failed ? material::Error()
|
||||
: s_op_outcome == OpOutcome::Uncertain ? material::Warning()
|
||||
: material::OnSurfaceMedium();
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(stCol));
|
||||
if (inFlight)
|
||||
ImGui::Text("%s%s", s_status_message.c_str(), material::LoadingDots()); // spinner while scanning
|
||||
else
|
||||
ImGui::TextWrapped("%s", s_status_message.c_str());
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::Spacing();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user