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
parent 4ee0f524d4
commit c7e48c16f8
5 changed files with 102 additions and 66 deletions

View File

@@ -26,7 +26,7 @@ set(DRAGONX_VERSION_SUFFIX "")
# ObsidianDragonLite is versioned INDEPENDENTLY of the full-node app above. The active variant's
# version flows to the generated header, the Windows .rc/manifest, and build.sh's release names via
# DRAGONX_APP_VERSION* (resolved in the lite/full block below).
set(DRAGONX_LITE_VERSION "1.0.0")
set(DRAGONX_LITE_VERSION "1.1.0")
set(DRAGONX_LITE_VERSION_SUFFIX "")
# C++17 standard

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) ──────────────────────────────────────────

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

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;

View File

@@ -1564,6 +1564,7 @@ void I18n::loadBuiltinEnglish()
strings_["faq"] = "FAQ";
strings_["faq_title"] = "Help & FAQ";
strings_["faq_intro"] = "Answers about your wallet and the DragonX node. Search, or browse by topic below.";
strings_["faq_intro_lite"] = "Answers about your Lite wallet. Search, or browse by topic below.";
strings_["faq_search_hint"] = "Search the FAQ\xE2\x80\xA6";
strings_["faq_open_tooltip"] = "Help & FAQ";
strings_["faq_no_results"] = "No results. Try a different search term.";
@@ -1595,9 +1596,9 @@ void I18n::loadBuiltinEnglish()
strings_["faq_w_gs_2_q"] = "How do I create a new wallet?";
strings_["faq_w_gs_2_a"] = "On first launch the setup wizard creates a wallet for you automatically. New wallets are backed by a secret recovery phrase (a list of words). Write that phrase down and keep it offline \xE2\x80\x94 it is the only way to restore your funds if this computer is lost.\n\nYou can review or back up the phrase any time from Settings > Backup & Data > Seed phrase.";
strings_["faq_w_gs_3_q"] = "How do I restore a wallet I already have?";
strings_["faq_w_gs_3_a"] = "Use the seed-phrase restore flow if you have a recovery phrase, or copy an existing wallet.dat file into the wallet's data directory before launch.\n\nAfter restoring, the node re-scans the blockchain to find your past transactions, so your balance and history may take a while to appear the first time.";
strings_["faq_w_gs_3_a"] = "Use the seed-phrase restore flow if you have a recovery phrase. (On the full-node build you can also copy an existing wallet.dat file into the wallet's data directory before launch.)\n\nAfter restoring, the wallet re-scans to find your past transactions, so your balance and history may take a while to appear the first time.";
strings_["faq_w_gs_4_q"] = "What does the first-run setup do?";
strings_["faq_w_gs_4_a"] = "The wizard lets you pick an appearance/theme, optionally download a bootstrap to speed up the initial blockchain sync, and set up encryption and a PIN. You can skip any step and change all of these later in Settings.";
strings_["faq_w_gs_4_a"] = "The wizard lets you pick an appearance/theme, set up encryption and a PIN, and \xE2\x80\x94 on the full-node build \xE2\x80\x94 optionally download a bootstrap to speed up the first sync. You can skip any step and change all of these later in Settings.";
// Wallet > Addresses & Privacy
strings_["faq_w_addr_1_q"] = "What's the difference between transparent and shielded addresses?";
@@ -1617,11 +1618,11 @@ void I18n::loadBuiltinEnglish()
strings_["faq_w_send_3_q"] = "Can I attach a message to a payment?";
strings_["faq_w_send_3_a"] = "Yes \xE2\x80\x94 when sending to a shielded (z) address you can include an encrypted memo. Only the recipient can read it. Transparent addresses do not support memos.";
strings_["faq_w_send_4_q"] = "I sent a payment but it says pending \xE2\x80\x94 why?";
strings_["faq_w_send_4_a"] = "A transaction is 'pending' until it is mined into a block and gains confirmations. This usually takes a minute or two. Shielded transactions also need the node to be synced to build the proof.\n\nIf a send stays pending unusually long, check that the node is connected and synced (see the status bar at the bottom of the window).";
strings_["faq_w_send_4_a"] = "A transaction is 'pending' until it is mined into a block and gains confirmations. This usually takes a minute or two. Shielded transactions also need your wallet to be synced to build the proof.\n\nIf a send stays pending unusually long, check that your wallet is connected and synced (see the status bar at the bottom of the window).";
// Wallet > Balance & Sync
strings_["faq_w_bal_1_q"] = "Why is my balance 0 or not updating?";
strings_["faq_w_bal_1_a"] = "While the node is still syncing the blockchain, your balance is incomplete and may read 0 \xE2\x80\x94 the wallet hasn't scanned all of your transactions yet. It fills in once syncing finishes.\n\nWatch the status bar at the bottom: it shows the connection state and block height. Once the node is synced, your balance is accurate.";
strings_["faq_w_bal_1_a"] = "While your wallet is still syncing, your balance is incomplete and may read 0 \xE2\x80\x94 it hasn't scanned all of your transactions yet. It fills in once syncing finishes.\n\nWatch the status bar at the bottom: it shows the connection state and block height. Once syncing is complete, your balance is accurate.";
strings_["faq_w_bal_2_q"] = "What are confirmations?";
strings_["faq_w_bal_2_a"] = "Each new block mined on top of the block containing your transaction adds one confirmation. More confirmations mean the payment is more firmly settled. Received funds become spendable after the first confirmation.";
strings_["faq_w_bal_3_q"] = "What's the difference between total and spendable balance?";
@@ -1631,7 +1632,7 @@ void I18n::loadBuiltinEnglish()
strings_["faq_w_sec_1_q"] = "How do I encrypt my wallet?";
strings_["faq_w_sec_1_a"] = "Open Settings > Node & Security and set a passphrase (the SECURITY section). Encryption protects your private keys on disk, so someone with access to the files still cannot spend your coins.\n\nChoose a strong passphrase and don't lose it \xE2\x80\x94 there is no way to recover an encrypted wallet without it.";
strings_["faq_w_sec_2_q"] = "What is the PIN / lock screen?";
strings_["faq_w_sec_2_a"] = "The PIN locks the app's screen so balances and actions are hidden when you step away. It's a convenience lock on top of encryption; you set it up during the wizard or in Settings > Node & Security (encrypt the wallet first).";
strings_["faq_w_sec_2_a"] = "The PIN locks the app's screen so balances and actions are hidden when you step away. It's a convenience lock on top of encryption; you set it up during the wizard or in Settings, after encrypting the wallet.";
strings_["faq_w_sec_3_q"] = "I forgot my passphrase \xE2\x80\x94 can it be recovered?";
strings_["faq_w_sec_3_a"] = "No. Wallet encryption cannot be bypassed. If you still have your seed recovery phrase, you can restore the wallet from it into a fresh wallet and set a new passphrase. Without either the passphrase or the seed phrase, the funds cannot be recovered.";
@@ -1641,9 +1642,9 @@ void I18n::loadBuiltinEnglish()
strings_["faq_w_seed_2_q"] = "My wallet has no recovery phrase \xE2\x80\x94 can I add one?";
strings_["faq_w_seed_2_a"] = "Older (legacy) wallets weren't seed-based. The wallet can migrate a legacy wallet into a modern seed-backed one: it creates a new seed wallet and sweeps your funds into it. Look for 'Migrate to seed\xE2\x80\xA6' in Settings > Backup & Data. This feature needs the current daemon version.";
strings_["faq_w_seed_3_q"] = "How do I restore from my recovery phrase?";
strings_["faq_w_seed_3_a"] = "Use the seed-restore flow when setting up a wallet and enter your words in order. The node then re-scans the chain to rebuild your balance and history, which can take a while the first time.";
strings_["faq_w_seed_3_a"] = "Use the seed-restore flow when setting up a wallet and enter your words in order. Your wallet then re-scans to rebuild your balance and history, which can take a while the first time.";
strings_["faq_w_seed_4_q"] = "Should I also back up wallet.dat?";
strings_["faq_w_seed_4_a"] = "Your seed phrase is the primary backup and is enough to restore everything. A copy of the wallet.dat file is a convenient secondary backup that also preserves labels and settings. Keep any backup offline and private.";
strings_["faq_w_seed_4_a"] = "Your seed phrase is the primary backup and is enough to restore everything. A copy of your wallet file is a convenient secondary backup that also preserves labels and settings. Keep any backup offline and private.";
// Wallet > Chat & Contacts
strings_["faq_w_chat_1_q"] = "What is the Chat feature?";
@@ -1654,6 +1655,17 @@ void I18n::loadBuiltinEnglish()
strings_["faq_w_chat_3_a"] = "Add a contact by their address in the Contacts tab, optionally with a name and avatar. Contacts can be kept private to the current wallet or shared across all wallets you open on this computer.";
strings_["faq_w_chat_4_q"] = "How do I hide, delete, or block a conversation?";
strings_["faq_w_chat_4_a"] = "Open a conversation and use the icons in its header. Hide (the eye) drops it from the list but keeps every message \xE2\x80\x94 a new message un-hides it. Delete (the trash) opens a dialog: 'Delete' clears the history on this device (if that person messages you again, the conversation comes back), while 'Delete & block' also stops their future messages until you unblock them from the 'Blocked' list above the conversation list. Everything here is local to this device \xE2\x80\x94 the messages stay on the blockchain and the other person keeps their own copy, so it isn't an 'unsend'.";
// ---- Lite-variant FAQ (ObsidianDragonLite): swapped-in answers + a Lite Wallet subcategory ----
strings_["faq_l_gs_1_q"] = "What is ObsidianDragonLite?";
strings_["faq_l_gs_1_a"] = "ObsidianDragonLite is the lightweight DragonX wallet. Instead of running a full node, it connects to a DragonX lite server, so it starts fast and doesn't download the whole blockchain.\n\nIt manages your keys, balances, and sending just like the full wallet \xE2\x80\x94 see the 'Lite Wallet' section below for how the server connection works and the privacy tradeoff.";
strings_["faq_l_sec_1_a"] = "Open Settings > Wallet and set a passphrase in the security section. Encryption protects your private keys on disk, so someone with access to the files still cannot spend your coins.\n\nChoose a strong passphrase and don't lose it \xE2\x80\x94 there is no way to recover an encrypted wallet without it.";
strings_["faq_l_lite_title"] = "Lite Wallet";
strings_["faq_l_lite_1_q"] = "How is the Lite wallet different from the full wallet?";
strings_["faq_l_lite_1_a"] = "The Lite wallet talks to a DragonX lite server instead of running its own full node. That makes it fast to start and light on disk and bandwidth, because it doesn't download or verify the entire blockchain itself. Your private keys always stay on your device \xE2\x80\x94 they are never sent to the server.";
strings_["faq_l_lite_2_q"] = "How do I choose or change the server?";
strings_["faq_l_lite_2_a"] = "Open Settings and pick a server. You can switch servers at any time and the wallet re-syncs from the new one. If a server is slow or unreachable, try another.";
strings_["faq_l_lite_3_q"] = "Is the Lite wallet as private as the full node?";
strings_["faq_l_lite_3_a"] = "Your keys never leave your device, and shielded amounts stay encrypted. But the lite server does see which addresses your wallet asks about and your IP address, so it can link your addresses together \xE2\x80\x94 a convenience-for-privacy tradeoff. For the most privacy, use the full-node wallet, which verifies the chain itself and doesn't reveal your addresses to a server.";
// Wallet > Settings & Appearance
strings_["faq_w_set_1_q"] = "How do I change the theme?";