fix(wallets): cap card at viewport + flex list so many wallets never clip

The wallets dialog hand-computed a content-sized card height. With many wallets
on a small / HiDPI (150%) window the computed height could exceed the viewport,
and the framework clips a too-tall card — footer and all.

Cap the card at 0.86 of the viewport height. When capped, the wallet list flexes
to the space left above the create/scan/footer controls and scrolls internally,
so those controls stay pinned and visible; uncapped (the common case, few
wallets) nothing changes and there's no spurious scrollbar. Add a
modal-wallets-many sweep surface (8 wallets, self-cleaning teardown) to exercise
the capped-scroll path. Verified at 100% + 150%.

This is the one real fixed-height clip risk from the modal audit; the other 44
modals use auto-height (grow-to-fit, capped at a viewport ratio) and were
confirmed non-clipping — e.g. the 24-word seed-backup grid renders fully at 150%
in a 720px window.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-10 14:47:59 -05:00
parent f8fbc32463
commit 3df516be70
2 changed files with 46 additions and 2 deletions

View File

@@ -354,6 +354,38 @@ void App::buildSweepCatalog()
ui::WalletsDialog::show(&a);
},
[](App&) { ui::WalletsDialog::hide(); });
// Many wallets — exercises the viewport-cap path: the card can't fit all rows, so the list
// must scroll internally while the create/scan/footer controls stay pinned (esp. at 150%).
// Teardown removes the extra files it dropped so the plain modal-wallets surface stays a
// clean 3-wallet reference (both surfaces share the one throwaway datadir).
static const char* kManyExtra[] = {"wallet-cold.dat", "wallet-trading.dat", "wallet-mining.dat",
"wallet-donations.dat", "wallet-2023.dat", "wallet-payroll.dat"};
add("modal-wallets-many", ui::NavPage::Settings,
[](App& a) {
std::error_code ec;
const std::string dd = util::Platform::getDragonXDataDir();
fs::create_directories(dd, ec);
const char* names[] = {"wallet.dat", "wallet-savings.dat", "wallet-cold.dat",
"wallet-trading.dat", "wallet-mining.dat", "wallet-donations.dat",
"wallet-2023.dat", "wallet-payroll.dat"};
for (int i = 0; i < 8; ++i) {
if (!fs::exists(dd + "/" + names[i], ec))
std::ofstream(dd + "/" + names[i], std::ios::binary) << std::string(64000 + i * 4096, '\0');
data::WalletIndexEntry e; e.fileName = names[i]; e.displayName = names[i];
e.cachedBalance = 5.0 * (i + 1); e.cachedAddressCount = 2 + i;
e.lastOpenedEpoch = 1720000000 - i * 86400; e.syncedHere = true;
a.walletIndex().upsert(e);
}
if (a.settings()) a.settings()->setActiveWalletFile("wallet.dat");
ui::WalletsDialog::show(&a);
},
[](App& a) {
ui::WalletsDialog::hide();
std::error_code ec;
const std::string dd = util::Platform::getDragonXDataDir();
for (const char* n : kManyExtra) fs::remove(dd + "/" + n, ec);
});
}
// First-run wizard — one per meaningful phase (full-node only; blocks all other UI while shown).