feat(chat): explain the no-identity state and why a send is stuck
- Chat tab no longer shows "Unlock your wallet" for every missing-identity
case. A new App::chatIdentityStatus() distinguishes Locked (real),
Preparing (unlocked, deriving the identity) and Unavailable (view-only or a
legacy wallet with no exportable secret — correctly latched). Each gets its
own icon + message, and the Unavailable prompt points at Back up / Migrate
instead of telling the user to unlock an already-unlocked wallet.
- A chat message that sits queued ("Sending") because the wallet is offline or
has no shielded funds for the fee now surfaces the reason (throttled, after a
short dwell), wiring the ready-made chat_toast_not_connected /
chat_toast_need_funds strings that were never shown. The send stays queued
and goes out when the condition clears.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+41
-1
@@ -4215,6 +4215,14 @@ void App::resetChatSession()
|
||||
++chat_session_generation_;
|
||||
}
|
||||
|
||||
App::ChatIdentityStatus App::chatIdentityStatus() const
|
||||
{
|
||||
if (chat_service_.hasIdentity()) return ChatIdentityStatus::Ready;
|
||||
if (state_.isLocked()) return ChatIdentityStatus::Locked;
|
||||
if (chat_identity_unavailable_) return ChatIdentityStatus::Unavailable;
|
||||
return ChatIdentityStatus::Preparing; // fetch in flight, or waiting on the node / address list
|
||||
}
|
||||
|
||||
void App::maybeProvisionChatIdentity()
|
||||
{
|
||||
if (!chat::hushChatFeatureEnabledAtBuild()) return; // constexpr — folds away in OFF builds
|
||||
@@ -5059,11 +5067,43 @@ void App::onChatBroadcastResult(const LiteInflightOp& op, bool ok, const std::st
|
||||
wallet::liteLog("chat: send FAILED: " + error);
|
||||
}
|
||||
|
||||
// A queued chat message stays "Sending" and auto-retries while the wallet is offline or can't cover the
|
||||
// fee — correct, but silent. Tell the user WHY: once per cause, after a short dwell (so brief blips stay
|
||||
// quiet) and then throttled, reusing the ready-made strings that were never wired up. Informational only;
|
||||
// the message is still queued and goes out when the condition clears.
|
||||
void App::maybeNoticeStuckChatSend() {
|
||||
int reason = 0; // 1 = offline, 2 = no shielded funds for the fee
|
||||
if (!chat_send_queue_.empty() && !state_.isLocked()) {
|
||||
if (!state_.connected) {
|
||||
reason = 1;
|
||||
} else if (inflight_op_.kind == LiteOpKind::None && chatPayFromZaddr(kChatMinFeeDrgx).empty()) {
|
||||
// Connected, nothing in flight, and no z-note can cover the fee → genuinely unfunded (not
|
||||
// merely a split/refill confirming, which chatPayFromZaddr/inflight_op_ would reflect).
|
||||
reason = 2;
|
||||
}
|
||||
}
|
||||
if (reason == 0) { chat_stuck_cause_ = 0; return; }
|
||||
|
||||
const double now = ImGui::GetTime();
|
||||
if (reason != chat_stuck_cause_) { // a new cause → restart the dwell + throttle
|
||||
chat_stuck_cause_ = reason;
|
||||
chat_stuck_since_ = now;
|
||||
chat_stuck_notice_at_ = 0.0;
|
||||
}
|
||||
if (now - chat_stuck_since_ < 15.0) return; // dwell: ignore transient states
|
||||
if (now < chat_stuck_notice_at_) return; // throttle repeats
|
||||
chat_stuck_notice_at_ = now + 45.0;
|
||||
ui::Notifications::instance().warning(
|
||||
TR(reason == 1 ? "chat_toast_not_connected" : "chat_toast_need_funds"));
|
||||
}
|
||||
|
||||
// Per-frame pump: drain the chat queue against verified notes (priority 1); otherwise build/refill the
|
||||
// buffer during idle (priority 2). One op per frame — the single broadcast channel serializes the rest.
|
||||
void App::pumpChatNoteBuffer() {
|
||||
if (!chat::hushChatFeatureEnabledAtBuild()) return;
|
||||
if (!chat_service_.hasIdentity() || !state_.connected) return;
|
||||
if (!chat_service_.hasIdentity()) { chat_stuck_cause_ = 0; return; }
|
||||
maybeNoticeStuckChatSend(); // runs even while offline — that's one of the reasons we report
|
||||
if (!state_.connected) return;
|
||||
// A locked wallet can't spend: submitting would return a non-transient lock error and flap the queued
|
||||
// message to Failed. Hold everything (send + split) until unlock — messages stay "Sending".
|
||||
if (state_.isLocked()) return;
|
||||
|
||||
Reference in New Issue
Block a user