feat(debug): full UI screenshot sweep — captures modals/dialogs/flows/states

The existing sweep only captures tabs. This adds a "Full UI sweep" (DEBUG
OPTIONS) that also drives every normally-hidden surface into view and captures
it under every skin, so all the UI a normal sweep misses can be reviewed at
once.

- The capture unit becomes a "surface" (SweepTarget: a base tab, optionally
  with a setup()/teardown() that forces a modal / multi-step flow / state
  overlay on top). Tabs are surfaces with a null setup. The existing tab sweep
  is folded into the same state machine (one code path). All sweep logic +
  the catalog + demo data move into a new src/app_sweep.cpp.
- Runs OFFLINE against injected demo data (installDemoWalletData snapshots then
  restores the real state_ — WalletState isn't copy-assignable, so a targeted
  field snapshot is used) and fires NO live op: capture_mode_ guards the
  seed-backup RPC, auto-lock, and the migration pumps; the async-firing dialogs
  are neutralized by pre-setting their state (fetch_started_ + demo phrase;
  seed_migration_step_ set directly). Verified: 0 RPC/secret/send calls.
- Catalog (v1): tabs + the bool-flag modals (import/export key, backup,
  seed-backup, about, settings) + the 6 migrate-to-seed steps + 4 wizard steps
  + lock/warmup/not-ready overlays + the send-confirm popup (via a new
  ui::SweepShowSendConfirm debug hook, since its state is send_tab-static).
- Overlay/blur surfaces settle 8 frames (blur backdrop freeze); setup re-runs
  each frame (OpenPopup popups must re-fire). updateScreenshotSweep now runs
  before the first-run-wizard early-return in render() so the sweep advances
  while a wizard step is shown. main.cpp drops vsync during a sweep so it isn't
  throttled to the compositor's unfocused rate. Output:
  <config>/screenshots-full/<surface>/<skin>.png + index.md.
- DRAGONX_FULL_SWEEP=1 runs the sweep headlessly then quits (CI / verification).

Verified end-to-end under WSLg: 288 PNGs (9 skins x 32 surfaces), all 1200x720,
[Sweep] done, clean quit, no daemon. Byte-identity across runs holds only for
static skins on data-free surfaces — the rest vary by animated theme effects /
live price / relative timestamps (expected for a review tool, not a bug).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-07 16:57:54 -05:00
parent 5228da707f
commit a86cb8f0f3
11 changed files with 500 additions and 112 deletions

View File

@@ -1272,6 +1272,11 @@ void App::handleGlobalShortcuts()
void App::render()
{
// Advance the screenshot sweep FIRST — before the first-run-wizard early-return below — so the
// sweep keeps progressing (and re-forces its target's state) even while a wizard step is shown.
// (Pins current_page_ too, before the sidebar reads it further down.)
updateScreenshotSweep();
// First-run wizard gate — blocks all normal UI
if (wizard_phase_ != WizardPhase::None && wizard_phase_ != WizardPhase::Done) {
renderFirstRunWizard();
@@ -1303,9 +1308,6 @@ void App::render()
}
}
// Debug screenshot sweep — pins the current (skin,page) and arms capture once settled. Must run
// before the sidebar reads current_page_ (below) so the forced page is reflected.
updateScreenshotSweep();
// While any full-window BLUR overlay is open: (1) suppress panel theme-effects for the whole
// frame so their foreground-draw-list borders (sidebar included) don't bleed over the overlay;
@@ -2969,8 +2971,9 @@ void App::renderSeedBackupDialog()
show_seed_backup_ = false;
};
// Kick off the async z_exportmnemonic fetch once, and only while unlocked.
if (!seed_backup_fetch_started_ && !state_.isLocked()) {
// Kick off the async z_exportmnemonic fetch once, and only while unlocked. Never during a UI
// sweep — the sweep pre-sets fetch_started_ + a demo phrase, but guard the RPC too.
if (!seed_backup_fetch_started_ && !state_.isLocked() && !capture_mode_) {
seed_backup_fetch_started_ = true;
seed_backup_loading_ = true;
seed_backup_no_mnemonic_ = false;