feat(chat): pre-split note buffer for rapid sends + status + console logging
Each chat message is a shielded tx that spends a verified note, so a burst of messages hit "insufficient verified funds" once the single note is spent. Add a per-frame note-buffer coordinator (both variants) that keeps ~10 verified spendable notes: it serializes sends through the one broadcast channel, counts verified notes by block depth (lite) or a rate-limited z_listunspent scan (full node), self-splits in the background to refill toward the target, and queues overflow to drain as change matures — with honest Sent/Failed status instead of the prior optimistic "Sent". Guardrails: single split in flight with a watchdog, cooldown, and session-generation guards so a wallet switch can't drain another wallet's queue. Surface a "Chat buffer: N/10 ready" indicator in the status bar while the Chat tab is active, and route chat diagnostics through the console on both variants (the full-node console now drains the shared ring). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
52
src/app.cpp
52
src/app.cpp
@@ -735,20 +735,40 @@ void App::update()
|
||||
// HushChat (lite): harvest chat memos from the refreshed transactions and thread them
|
||||
// (no-op when the feature is off or no identity; the store dedups across refreshes).
|
||||
ingestLiteChatMemos(liteModel);
|
||||
// Chat note-buffer: recompute the verified/maturing self-note estimates from the fresh model.
|
||||
refreshChatNoteBudget(liteModel);
|
||||
}
|
||||
// Deliver a completed async send/shield result to the waiting send_tab callback.
|
||||
// Deliver a completed async send/shield result. Route by the in-flight op that owns the single
|
||||
// broadcast channel: a user Send-tab send goes to lite_send_callback_; a chat send / contact
|
||||
// request resolves its echo (real status — not the old optimistic "Sent"); a note-buffer split
|
||||
// just logs. Because the controller runs one broadcast at a time, inflight_op_ correlates the
|
||||
// one global result with no txid matching.
|
||||
wallet::LiteBroadcastResult broadcast;
|
||||
if (lite_wallet_->takeBroadcastResult(broadcast)) {
|
||||
// Mirror failures into the lite Console (copyable) in addition to the toast the send UI
|
||||
// shows — transient toasts are easy to miss and impossible to copy.
|
||||
if (!broadcast.ok)
|
||||
wallet::liteLog("Send/shield failed: " + broadcast.error);
|
||||
if (lite_send_callback_) {
|
||||
lite_send_callback_(broadcast.ok, broadcast.ok ? broadcast.txid : broadcast.error);
|
||||
lite_send_callback_ = nullptr;
|
||||
const LiteInflightOp finished = inflight_op_;
|
||||
inflight_op_ = LiteInflightOp{}; // channel is free again
|
||||
switch (finished.kind) {
|
||||
case LiteOpKind::ChatSend:
|
||||
case LiteOpKind::ContactRequest:
|
||||
onChatBroadcastResult(finished, broadcast.ok, broadcast.error);
|
||||
break;
|
||||
case LiteOpKind::Split:
|
||||
// Buffer split done; the next refresh re-counts the new (maturing) notes.
|
||||
break;
|
||||
case LiteOpKind::UserSend:
|
||||
case LiteOpKind::None:
|
||||
default:
|
||||
if (lite_send_callback_) {
|
||||
lite_send_callback_(broadcast.ok, broadcast.ok ? broadcast.txid : broadcast.error);
|
||||
lite_send_callback_ = nullptr;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Startup lock screen: once the first refresh reveals the (auto-opened) wallet is
|
||||
// encrypted, prompt to unlock if it's locked. Soft by design — balances stay viewable via
|
||||
// viewing keys while locked; only spending needs the passphrase, so the user may dismiss
|
||||
@@ -765,6 +785,13 @@ void App::update()
|
||||
// it can act; compiled away when the feature is off (constexpr gate inside).
|
||||
maybeProvisionChatIdentity();
|
||||
|
||||
// Chat note-buffer coordinator (BOTH variants). Full-node only: refresh the per-note counts via a
|
||||
// rate-limited z_listunspent worker scan (lite gets its counts from the refresh model, above). Then
|
||||
// pump: drain queued chat sends against verified notes and build/refill the buffer during idle. Both
|
||||
// self-gate to no-ops unless chat is engaged; the pump serializes to one send outstanding at a time.
|
||||
refreshChatNoteBudgetNode();
|
||||
pumpChatNoteBuffer();
|
||||
|
||||
// One-time reminder to back up the wallet's seed phrase (mnemonic wallets only).
|
||||
maybeRemindSeedBackup();
|
||||
|
||||
@@ -2324,17 +2351,32 @@ void App::renderStatusBar()
|
||||
// Connection / daemon status sits to the left of the version string
|
||||
// with a small gap.
|
||||
float gap = sbSectionGap;
|
||||
float occupiedX = versionX; // leftmost X used by the version + connection status so far
|
||||
if (!connection_status_.empty() && connection_status_ != "Connected") {
|
||||
float statusW = ImGui::CalcTextSize(connection_status_.c_str()).x;
|
||||
float statusX = versionX - statusW - gap;
|
||||
ImGui::SameLine(statusX);
|
||||
ImGui::TextDisabled("%s", connection_status_.c_str());
|
||||
occupiedX = statusX;
|
||||
} else if (!daemon_status_.empty() && daemon_status_.find("Error") != std::string::npos) {
|
||||
const char* errText = TR("sb_daemon_not_found");
|
||||
float statusW = ImGui::CalcTextSize(errText).x;
|
||||
float statusX = versionX - statusW - gap;
|
||||
ImGui::SameLine(statusX);
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.5f, 0.5f, 1.0f), "%s", errText);
|
||||
occupiedX = statusX;
|
||||
}
|
||||
|
||||
// Chat note-buffer status — only while the Chat tab is active. Sits left of the version/connection
|
||||
// block. Surfaces the buffer filling/draining (and doubles as a diagnostic for send readiness).
|
||||
if (current_page_ == ui::NavPage::Chat) {
|
||||
const std::string cb = chatBufferStatusText();
|
||||
if (!cb.empty()) {
|
||||
float cbW = ImGui::CalcTextSize(cb.c_str()).x;
|
||||
float cbX = occupiedX - cbW - gap;
|
||||
ImGui::SameLine(cbX);
|
||||
ImGui::TextUnformatted(cb.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
// Version always at far right
|
||||
|
||||
Reference in New Issue
Block a user