test(sweep): add UI-sweep targets for the new syncing / no-peers / chat-identity states

Capture the states the earlier verification couldn't reach:
- overview-syncing: the balance "catching up — funds are safe" strip (P4).
- overview-no-peers: the node-status banner's no-peers detail (P2; full-node
  only — renders as a plain Overview in the Lite sweep binary).
- chat-identity-preparing / -unavailable: the chat empty states that used to
  all say "unlock your wallet" (P3). Setup clears the fabricated demo identity
  (clearIdentity wipes only the key; the demo message store survives);
  sweepEnsureDemoChatIdentity() re-fabricates on teardown so later targets keep
  the populated chat UI.

Also make the demo baseline a genuinely synced node (tip_known=true) so the
no-peers banner only appears on its own target, and dismiss the lite first-run
welcome prompt during the demo so it stops occluding every lite screenshot
(snapshotted + restored). Verified: all four new captures render correctly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-10-17 17:27:22 -05:00
co-authored by Claude Opus 4.8
parent 9db73323b6
commit cd9ca4cd77
2 changed files with 74 additions and 0 deletions
+2
View File
@@ -1677,6 +1677,7 @@ private:
std::string sweep_sandbox_; std::string sweep_sandbox_;
bool sweep_chat_identity_fabricated_ = false; // seedChatDemoData() made the demo chat identity 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 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; void writeSweepManifest() const;
// Snapshot of the state_ fields the demo installer mutates, restored at sweep end. Kept as a // 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. // 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_price_usd=0;
double market_change_24h=0; double market_change_24h=0;
WizardPhase wizard_phase = WizardPhase::None; // the demo state dismisses the wizard 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_; } sweep_state_snapshot_;
bool sidebar_user_toggled_ = false; // user manually toggled — suppress auto-collapse bool sidebar_user_toggled_ = false; // user manually toggled — suppress auto-collapse
float sidebar_width_anim_ = 0.0f; // animated width (0 = uninitialized) float sidebar_width_anim_ = 0.0f; // animated width (0 = uninitialized)
+72
View File
@@ -38,6 +38,8 @@
#include "ui/windows/folder_picker.h" #include "ui/windows/folder_picker.h"
#include "ui/pages/settings_page.h" #include "ui/pages/settings_page.h"
#include "chat/chat_protocol.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/i18n.h"
#include "util/platform.h" #include "util/platform.h"
#include "util/private_temp_dir.h" #include "util/private_temp_dir.h"
@@ -159,11 +161,15 @@ void App::installDemoWalletData()
s.market_price_usd = state_.market.price_usd; s.market_price_usd = state_.market.price_usd;
s.market_change_24h = state_.market.change_24h; s.market_change_24h = state_.market.change_24h;
s.wizard_phase = wizard_phase_; s.wizard_phase = wizard_phase_;
s.lite_firstrun_dismissed = lite_firstrun_dismissed_;
s.valid = true; s.valid = true;
applyHealthyDemoState(); applyHealthyDemoState();
state_.sync.blocks = state_.sync.headers = 3124322; state_.sync.blocks = state_.sync.headers = 3124322;
state_.sync.verification_progress = 1.0; state_.sync.syncing = false; 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. // Legacy (pre-seed) wallet so the Settings "Migrate to seed" button glows in the sweep.
wallet_seed_status_ = WalletSeedStatus::NoMnemonic; wallet_seed_status_ = WalletSeedStatus::NoMnemonic;
@@ -260,6 +266,20 @@ void App::applyHealthyDemoState()
wizard_phase_ = WizardPhase::None; 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 // 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. // migration, wallet-switch, security-workflow and seed-backup state in memory, and its teardown resets it.
const char* App::fullSweepBlockedReason() const const char* App::fullSweepBlockedReason() const
@@ -307,6 +327,7 @@ void App::clearDemoWalletData()
state_.market.price_history = g_marketHistorySnapshot; state_.market.price_history = g_marketHistorySnapshot;
g_marketHistorySnapshot.clear(); g_marketHistorySnapshot.clear();
wizard_phase_ = s.wizard_phase; // applyHealthyDemoState() dismissed it wizard_phase_ = s.wizard_phase; // applyHealthyDemoState() dismissed it
lite_firstrun_dismissed_ = s.lite_firstrun_dismissed;
s = SweepStateSnapshot{}; // invalidate s = SweepStateSnapshot{}; // invalidate
// Sandbox: close what shows it, then delete it (error_code overloads only; at worst temp debris, // 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.state_.connected = false; a.state_.encryption_state_known = false; },
[](App& a) { a.applyHealthyDemoState(); }); [](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. // A mining (transparent) address as the recipient: shown as unpayable, Send disabled.
add("send-mining-recipient", ui::NavPage::Send, add("send-mining-recipient", ui::NavPage::Send,
[](App& a) { ui::SweepSetSendRecipient(&a, "R9NXAVJezHiBnT3ijTpg3JUZre7PxhJWti"); }, [](App& a) { ui::SweepSetSendRecipient(&a, "R9NXAVJezHiBnT3ijTpg3JUZre7PxhJWti"); },