feat(lite): version 1.1.0 + variant-aware FAQ

Bump DRAGONX_LITE_VERSION 1.0.0 -> 1.1.0: since lite-1.0.0 the Lite variant
gained the whole 2.0.x shared UI/UX + diagnostics + i18n + perf work and this
session's chat delete/block — a minor bump (new features, no breaking change).

Make the FAQ variant-aware (walletFaq(fullNode)) so the Lite build reads
correctly: "What is ObsidianDragonLite?", encryption in the Wallet tab (not
Node & Security), migrate-to-seed hidden, node-specific answers reworded
neutrally ("the wallet syncs"), a new "Lite Wallet" subcategory (server model +
privacy tradeoff) standing in for the hidden Daemon group, and the redundant
single "Wallet" group tab dropped when there's no Daemon group to switch to.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-02 01:12:08 -05:00
co-authored by Claude Opus 4.8
parent 4ee0f524d4
commit c7e48c16f8
5 changed files with 102 additions and 66 deletions
+69 -50
View File
@@ -9,57 +9,76 @@ namespace ui {
namespace faq {
// ── Wallet group ───────────────────────────────────────────────────────────
const std::vector<FaqSubcategory>& walletFaq()
// Variant-aware: gs_1 (what the app IS) and sec_1 (where encryption lives) differ between the
// full-node and Lite builds; migrate-to-seed (seed_2) is full-node only; and the Lite build gets a
// trailing "Lite Wallet" subcategory that stands in for the (hidden) Daemon group. The remaining
// answers are worded neutrally so one string serves both variants.
static std::vector<FaqSubcategory> buildWalletFaq(bool fullNode)
{
static const std::vector<FaqSubcategory> kWallet = {
{ "faq_w_gs_title", {
{ "faq_w_gs_1_q", "faq_w_gs_1_a" },
{ "faq_w_gs_2_q", "faq_w_gs_2_a" },
{ "faq_w_gs_3_q", "faq_w_gs_3_a" },
{ "faq_w_gs_4_q", "faq_w_gs_4_a" },
}},
{ "faq_w_addr_title", {
{ "faq_w_addr_1_q", "faq_w_addr_1_a" },
{ "faq_w_addr_2_q", "faq_w_addr_2_a" },
{ "faq_w_addr_3_q", "faq_w_addr_3_a" },
{ "faq_w_addr_4_q", "faq_w_addr_4_a" },
}},
{ "faq_w_send_title", {
{ "faq_w_send_1_q", "faq_w_send_1_a" },
{ "faq_w_send_2_q", "faq_w_send_2_a" },
{ "faq_w_send_3_q", "faq_w_send_3_a" },
{ "faq_w_send_4_q", "faq_w_send_4_a" },
}},
{ "faq_w_bal_title", {
{ "faq_w_bal_1_q", "faq_w_bal_1_a" },
{ "faq_w_bal_2_q", "faq_w_bal_2_a" },
{ "faq_w_bal_3_q", "faq_w_bal_3_a" },
}},
{ "faq_w_sec_title", {
{ "faq_w_sec_1_q", "faq_w_sec_1_a" },
{ "faq_w_sec_2_q", "faq_w_sec_2_a" },
{ "faq_w_sec_3_q", "faq_w_sec_3_a" },
}},
{ "faq_w_seed_title", {
{ "faq_w_seed_1_q", "faq_w_seed_1_a" },
{ "faq_w_seed_2_q", "faq_w_seed_2_a" },
{ "faq_w_seed_3_q", "faq_w_seed_3_a" },
{ "faq_w_seed_4_q", "faq_w_seed_4_a" },
}},
{ "faq_w_chat_title", {
{ "faq_w_chat_1_q", "faq_w_chat_1_a" },
{ "faq_w_chat_2_q", "faq_w_chat_2_a" },
{ "faq_w_chat_3_q", "faq_w_chat_3_a" },
{ "faq_w_chat_4_q", "faq_w_chat_4_a" },
}},
{ "faq_w_set_title", {
{ "faq_w_set_1_q", "faq_w_set_1_a" },
{ "faq_w_set_2_q", "faq_w_set_2_a" },
{ "faq_w_set_3_q", "faq_w_set_3_a" },
{ "faq_w_set_4_q", "faq_w_set_4_a" },
}},
};
return kWallet;
std::vector<FaqSubcategory> w;
w.push_back({ "faq_w_gs_title", {
{ fullNode ? "faq_w_gs_1_q" : "faq_l_gs_1_q", fullNode ? "faq_w_gs_1_a" : "faq_l_gs_1_a" },
{ "faq_w_gs_2_q", "faq_w_gs_2_a" },
{ "faq_w_gs_3_q", "faq_w_gs_3_a" },
{ "faq_w_gs_4_q", "faq_w_gs_4_a" },
}});
w.push_back({ "faq_w_addr_title", {
{ "faq_w_addr_1_q", "faq_w_addr_1_a" },
{ "faq_w_addr_2_q", "faq_w_addr_2_a" },
{ "faq_w_addr_3_q", "faq_w_addr_3_a" },
{ "faq_w_addr_4_q", "faq_w_addr_4_a" },
}});
w.push_back({ "faq_w_send_title", {
{ "faq_w_send_1_q", "faq_w_send_1_a" },
{ "faq_w_send_2_q", "faq_w_send_2_a" },
{ "faq_w_send_3_q", "faq_w_send_3_a" },
{ "faq_w_send_4_q", "faq_w_send_4_a" },
}});
w.push_back({ "faq_w_bal_title", {
{ "faq_w_bal_1_q", "faq_w_bal_1_a" },
{ "faq_w_bal_2_q", "faq_w_bal_2_a" },
{ "faq_w_bal_3_q", "faq_w_bal_3_a" },
}});
w.push_back({ "faq_w_sec_title", {
{ "faq_w_sec_1_q", fullNode ? "faq_w_sec_1_a" : "faq_l_sec_1_a" },
{ "faq_w_sec_2_q", "faq_w_sec_2_a" },
{ "faq_w_sec_3_q", "faq_w_sec_3_a" },
}});
{
std::vector<FaqEntry> seed = { { "faq_w_seed_1_q", "faq_w_seed_1_a" } };
if (fullNode) seed.push_back({ "faq_w_seed_2_q", "faq_w_seed_2_a" }); // migrate-to-seed is full-node only
seed.push_back({ "faq_w_seed_3_q", "faq_w_seed_3_a" });
seed.push_back({ "faq_w_seed_4_q", "faq_w_seed_4_a" });
w.push_back({ "faq_w_seed_title", std::move(seed) });
}
w.push_back({ "faq_w_chat_title", {
{ "faq_w_chat_1_q", "faq_w_chat_1_a" },
{ "faq_w_chat_2_q", "faq_w_chat_2_a" },
{ "faq_w_chat_3_q", "faq_w_chat_3_a" },
{ "faq_w_chat_4_q", "faq_w_chat_4_a" },
}});
w.push_back({ "faq_w_set_title", {
{ "faq_w_set_1_q", "faq_w_set_1_a" },
{ "faq_w_set_2_q", "faq_w_set_2_a" },
{ "faq_w_set_3_q", "faq_w_set_3_a" },
{ "faq_w_set_4_q", "faq_w_set_4_a" },
}});
// Lite-only: explain the server model (stands in for the hidden Daemon group).
if (!fullNode) {
w.push_back({ "faq_l_lite_title", {
{ "faq_l_lite_1_q", "faq_l_lite_1_a" },
{ "faq_l_lite_2_q", "faq_l_lite_2_a" },
{ "faq_l_lite_3_q", "faq_l_lite_3_a" },
}});
}
return w;
}
const std::vector<FaqSubcategory>& walletFaq(bool fullNode)
{
static const std::vector<FaqSubcategory> kFull = buildWalletFaq(true);
static const std::vector<FaqSubcategory> kLite = buildWalletFaq(false);
return fullNode ? kFull : kLite;
}
// ── Daemon group (full-node only) ──────────────────────────────────────────
+4 -1
View File
@@ -33,7 +33,10 @@ struct FaqSubcategory {
// The two top-level groups. daemonFaq() is full-node material and is only shown when
// the build supports full-node lifecycle actions (see App::supportsFullNodeLifecycleActions()).
const std::vector<FaqSubcategory>& walletFaq();
// walletFaq() is variant-aware: pass fullNode=false for the Lite variant, which swaps in
// lite-appropriate answers (no local node / daemon), drops full-node-only entries
// (e.g. migrate-to-seed), and appends a "Lite Wallet" subcategory explaining the server model.
const std::vector<FaqSubcategory>& walletFaq(bool fullNode);
const std::vector<FaqSubcategory>& daemonFaq();
} // namespace faq
+9 -7
View File
@@ -84,16 +84,17 @@ void RenderFaqDialog(App* app, bool* p_open)
if (ImGui::IsKeyPressed(ImGuiKey_Escape)) *p_open = false;
// Subtitle under the plain heading, matching the Wallets dialog's intro caption.
material::Type().textColored(material::TypeStyle::Caption, material::OnSurfaceMedium(), TR("faq_intro"));
material::Type().textColored(material::TypeStyle::Caption, material::OnSurfaceMedium(),
TR(daemonAvailable ? "faq_intro" : "faq_intro_lite"));
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
const float contentW = ImGui::GetContentRegionAvail().x;
// ── Group tabs: Wallet | Daemon ──
{
const int nTabs = daemonAvailable ? 2 : 1;
// ── Group tabs: Wallet | Daemon — only when there's a choice. Lite has just the Wallet group, so a
// lone "Wallet" selector is redundant; skip it entirely (the group is already pinned to 0 above).
if (daemonAvailable) {
const float gap = ImGui::GetStyle().ItemSpacing.x;
const float tabW = (contentW - gap * (nTabs - 1)) / static_cast<float>(nTabs);
const float tabW = (contentW - gap) / 2.0f;
auto tab = [&](const char* label, int idx) {
const bool active = (s_faq.group == idx);
if (active) {
@@ -104,7 +105,8 @@ void RenderFaqDialog(App* app, bool* p_open)
if (active) ImGui::PopStyleColor(2);
};
tab(TR("faq_group_wallet"), 0);
if (daemonAvailable) { ImGui::SameLine(); tab(TR("faq_group_daemon"), 1); }
ImGui::SameLine();
tab(TR("faq_group_daemon"), 1);
}
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
@@ -131,7 +133,7 @@ void RenderFaqDialog(App* app, bool* p_open)
material::ApplySmoothScroll();
ImDrawList* dl = ImGui::GetWindowDrawList();
const auto& groups = (s_faq.group == 1 && daemonAvailable) ? faq::daemonFaq() : faq::walletFaq();
const auto& groups = (s_faq.group == 1 && daemonAvailable) ? faq::daemonFaq() : faq::walletFaq(daemonAvailable);
bool anyShown = false;
bool firstSection = true;