feat(chat): fee floor, real delivery status, pay-from-funded, funds pre-check
Chat sends move 0 value, so the network fee is structurally load-bearing (it's the only thing that forces a real shielded input; 0-value + 0-fee builds a degenerate, unrelayable tx). Three gaps addressed: 1. Fee floor — broadcastChatMemos now uses max(getDefaultFee(), kChatMinFeeDrgx), so a 0 / too-low global default-fee setting can't silently break chat. 2. Real delivery status — the echo was marked Sent on SUBMIT regardless of the on-chain outcome (the z_sendmany callback was empty), so failures were invisible and the Retry affordance never fired for async failures. Add a third ChatDelivery::Sending state (appended so persisted 0=Sent stays valid); record the echo in-memory as Sending, and resolve it to Sent/Failed from the z_sendmany completion callback — persisting only the final status (so a restart never shows a stuck spinner; a stray persisted Sending loads as Sent). A subtle "sending…" label shows while in flight. 3. Pay-from-funded + pre-check — z_sendmany spends from one z-address, and the identity reply address may be unfunded while funds sit elsewhere. chatPayFromZaddr picks a spendable z-address that can cover the fee (preferring the identity address); the memo still advertises the identity address as reply-to, so paying from a different note is transport-transparent. If nothing can cover the fee, a clear "need a small shielded balance" toast replaces the cryptic failure. Full node only for the callback path; lite resolves optimistically on queue. 8-language strings + CJK subset (+1 glyph 賄). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -460,6 +460,7 @@ void RenderChatTab(App* app)
|
||||
const bool outgoing = (m.direction == chat::ChatDirection::Outgoing);
|
||||
const bool request = (m.kind == chat::ChatMessageKind::ContactRequest);
|
||||
const bool failed = outgoing && m.delivery == chat::ChatDelivery::Failed;
|
||||
const bool sending = outgoing && m.delivery == chat::ChatDelivery::Sending;
|
||||
const std::int64_t minute = m.timestamp / 60;
|
||||
const bool startGroup = (prevDir != static_cast<int>(outgoing)) || (minute != prevMin);
|
||||
|
||||
@@ -514,6 +515,15 @@ void RenderChatTab(App* app)
|
||||
ImGui::SameLine();
|
||||
if (ImGui::SmallButton(TR("chat_retry"))) { app->sendChatMessage(sel->cid, m.body); s_scroll_to_cid = sel->cid; }
|
||||
}
|
||||
// In-flight send → subtle right-aligned "sending…"; the op callback resolves it.
|
||||
if (sending) {
|
||||
ImGui::Dummy(ImVec2(0.0f, 1.0f * dp));
|
||||
const float sw = metaFont->CalcTextSizeA(metaSz, FLT_MAX, 0.0f, TR("chat_sending")).x;
|
||||
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + std::max(0.0f, availW - sw));
|
||||
ImGui::PushFont(metaFont); ImGui::PushStyleColor(ImGuiCol_Text, material::OnSurfaceMedium());
|
||||
ImGui::TextUnformatted(TR("chat_sending"));
|
||||
ImGui::PopStyleColor(); ImGui::PopFont();
|
||||
}
|
||||
ImGui::Dummy(ImVec2(0.0f, 5.0f * dp));
|
||||
ImGui::PopID();
|
||||
prevMin = minute; prevDir = static_cast<int>(outgoing);
|
||||
|
||||
Reference in New Issue
Block a user