diff --git a/src/app.h b/src/app.h index 5c13184..aaba7ff 100644 --- a/src/app.h +++ b/src/app.h @@ -1677,6 +1677,7 @@ private: std::string sweep_sandbox_; bool sweep_chat_identity_fabricated_ = false; // seedChatDemoData() made the demo chat identity void applyHealthyDemoState(); // reset the connection/encryption flags to the healthy demo values + void sweepEnsureDemoChatIdentity(); // re-fabricate a demo chat identity if none (sweep teardown) void writeSweepManifest() const; // Snapshot of the state_ fields the demo installer mutates, restored at sweep end. Kept as a // plain struct because WalletState has reference-alias members and isn't copy-assignable. @@ -1692,6 +1693,7 @@ private: double market_price_usd=0; double market_change_24h=0; WizardPhase wizard_phase = WizardPhase::None; // the demo state dismisses the wizard + bool lite_firstrun_dismissed = false; // the demo dismisses the lite welcome prompt } sweep_state_snapshot_; bool sidebar_user_toggled_ = false; // user manually toggled — suppress auto-collapse float sidebar_width_anim_ = 0.0f; // animated width (0 = uninitialized) diff --git a/src/app_sweep.cpp b/src/app_sweep.cpp index f932eca..a0a9b8b 100644 --- a/src/app_sweep.cpp +++ b/src/app_sweep.cpp @@ -38,6 +38,8 @@ #include "ui/windows/folder_picker.h" #include "ui/pages/settings_page.h" #include "chat/chat_protocol.h" +#include "chat/chat_identity.h" // deriveChatIdentityFromSecret / ChatIdentityStatus (sweep identity) +#include "chat/chat_crypto.h" // ChatKeyPair / wipeChatKeyPair #include "util/i18n.h" #include "util/platform.h" #include "util/private_temp_dir.h" @@ -159,11 +161,15 @@ void App::installDemoWalletData() s.market_price_usd = state_.market.price_usd; s.market_change_24h = state_.market.change_24h; s.wizard_phase = wizard_phase_; + s.lite_firstrun_dismissed = lite_firstrun_dismissed_; s.valid = true; applyHealthyDemoState(); state_.sync.blocks = state_.sync.headers = 3124322; state_.sync.verification_progress = 1.0; state_.sync.syncing = false; + state_.sync.tip_known = true; // a healthy synced node knows the peer-derived tip (so the no-peers + state_.sync.building_witnesses = false; state_.sync.rescanning = false; // banner only shows on its target + lite_firstrun_dismissed_ = true; // the demo "has" a wallet — don't overlay the lite welcome prompt // Legacy (pre-seed) wallet so the Settings "Migrate to seed" button glows in the sweep. wallet_seed_status_ = WalletSeedStatus::NoMnemonic; @@ -260,6 +266,20 @@ void App::applyHealthyDemoState() wizard_phase_ = WizardPhase::None; } +// Re-fabricate an identity-only demo chat identity (no messages — the demo store is untouched by +// clearIdentity) so a chat-identity sweep target's teardown restores the populated chat UI. Mirrors the +// identity half of seedChatDemoData(); never clobbers a real identity (only runs when none is held). +void App::sweepEnsureDemoChatIdentity() +{ + if (!chat::hushChatFeatureEnabledAtBuild()) return; + if (chat_service_.hasIdentity()) return; + chat::ChatKeyPair keys; + const std::string demoSecret = generateChatLocalId("demo-chat:", 24); // random, per-run + if (chat::deriveChatIdentityFromSecret(demoSecret, keys).status == chat::ChatIdentityStatus::Ready) + chat_service_.setIdentity(keys); + chat::wipeChatKeyPair(keys); +} + // Refuse a full sweep while any real workflow it would fake is in flight: its surfaces overwrite the // migration, wallet-switch, security-workflow and seed-backup state in memory, and its teardown resets it. const char* App::fullSweepBlockedReason() const @@ -307,6 +327,7 @@ void App::clearDemoWalletData() state_.market.price_history = g_marketHistorySnapshot; g_marketHistorySnapshot.clear(); wizard_phase_ = s.wizard_phase; // applyHealthyDemoState() dismissed it + lite_firstrun_dismissed_ = s.lite_firstrun_dismissed; s = SweepStateSnapshot{}; // invalidate // Sandbox: close what shows it, then delete it (error_code overloads only; at worst temp debris, @@ -703,6 +724,57 @@ void App::buildSweepCatalog() [](App& a) { a.state_.connected = false; a.state_.encryption_state_known = false; }, [](App& a) { a.applyHealthyDemoState(); }); + // Restore the healthy synced demo sync-state (applyHealthyDemoState leaves state_.sync alone). + auto resetDemoSync = [](App& a) { + a.applyHealthyDemoState(); + a.state_.sync.syncing = false; a.state_.sync.building_witnesses = false; + a.state_.sync.rescanning = false; a.state_.sync.verification_progress = 1.0; + a.state_.sync.tip_known = true; + a.state_.sync.blocks = a.state_.sync.headers = 3124322; + }; + // Balance tab while the node is still catching up (connected, tip known, verifying) → the shared + // "catching up — your funds are safe" strip above whichever layout renders (P4). + add("overview-syncing", ui::NavPage::Overview, + [](App& a) { + a.applyHealthyDemoState(); + a.state_.sync.tip_known = true; + a.state_.sync.syncing = true; + a.state_.sync.building_witnesses = false; + a.state_.sync.rescanning = false; + a.state_.sync.verification_progress = 0.62; + a.state_.sync.blocks = 1934000; a.state_.sync.headers = 3124322; + }, + resetDemoSync); + // Full node connected to the daemon but with zero network peers → the node-status banner's honest + // detail (P2). Full-node only: the Lite build has no node, so there this renders as a plain Overview. + add("overview-no-peers", ui::NavPage::Overview, + [](App& a) { + a.applyHealthyDemoState(); + a.state_.sync.tip_known = false; // no peer-derived tip → NoPeers banner + a.state_.sync.syncing = false; + a.state_.sync.blocks = 3124322; a.state_.sync.headers = 3124322; + a.connection_status_ = "Connected"; + }, + resetDemoSync); + + // Chat with no derived identity yet — the states that previously ALL said "unlock your wallet" (P3). + // Setup clears the fabricated demo identity (clearIdentity wipes only the key; the demo messages stay + // in the store); teardown re-fabricates an identity so the populated chat UI returns for later targets. + add("chat-identity-preparing", ui::NavPage::Chat, + [](App& a) { + a.chat_service_.clearIdentity(); + a.chat_identity_unavailable_ = false; + a.chat_identity_fetch_in_flight_ = false; + }, + [](App& a) { a.chat_identity_unavailable_ = false; a.sweepEnsureDemoChatIdentity(); }); + add("chat-identity-unavailable", ui::NavPage::Chat, + [](App& a) { + a.chat_service_.clearIdentity(); + a.chat_identity_unavailable_ = true; // view-only / legacy wallet with no exportable secret + a.chat_identity_fetch_in_flight_ = false; + }, + [](App& a) { a.chat_identity_unavailable_ = false; a.sweepEnsureDemoChatIdentity(); }); + // A mining (transparent) address as the recipient: shown as unpayable, Send disabled. add("send-mining-recipient", ui::NavPage::Send, [](App& a) { ui::SweepSetSendRecipient(&a, "R9NXAVJezHiBnT3ijTpg3JUZre7PxhJWti"); },