From 14f5fdc5596b274f68b201a40844666f30858826 Mon Sep 17 00:00:00 2001 From: DanS Date: Wed, 1 Jul 2026 15:02:15 -0500 Subject: [PATCH] fix(overview): mining addresses visible at 0 balance; drag-reorder always applies - Address list: a mining-flagged address stays visible even at 0 balance when "hide zero balances" is on (payout addresses shouldn't vanish). - Drag-reorder: persist dense sort orders (0..N-1) for the whole visible list on drop via App::reorderAddresses, instead of a pairwise swap that no-ops when both rows are still at the default un-ordered state. First drag now always takes effect, and explicit order keeps overriding the starred/type/balance sort. - Tests: 0-balance mining row survives hide-zero; ordered non-favorite outranks a favorite. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/app.h | 4 ++++ src/app_network.cpp | 8 ++++++++ src/ui/windows/balance_address_list.cpp | 3 ++- src/ui/windows/balance_components.cpp | 20 ++++++++++++++------ tests/test_phase4.cpp | 21 +++++++++++++++++++++ 5 files changed, 49 insertions(+), 7 deletions(-) diff --git a/src/app.h b/src/app.h index 6648ef7..363cec6 100644 --- a/src/app.h +++ b/src/app.h @@ -252,6 +252,10 @@ public: void setAddressSortOrder(const std::string& addr, int order); int getNextSortOrder() const; void swapAddressOrder(const std::string& a, const std::string& b); + // Assign dense sort orders (0..N-1) to the given addresses in the given order and + // persist once. Used by drag-reorder so a drop always takes effect (even from the + // default un-ordered state, where a pairwise swap would be a no-op). + void reorderAddresses(const std::vector& orderedAddrs); bool isMiningAddress(const std::string& addr) const; void setMiningAddress(const std::string& addr, bool mining); void invalidateAddressValidationCache(); diff --git a/src/app_network.cpp b/src/app_network.cpp index ff9efe1..8da98aa 100644 --- a/src/app_network.cpp +++ b/src/app_network.cpp @@ -2094,6 +2094,14 @@ void App::swapAddressOrder(const std::string& a, const std::string& b) } } +void App::reorderAddresses(const std::vector& orderedAddrs) +{ + if (!settings_) return; + for (size_t i = 0; i < orderedAddrs.size(); ++i) + settings_->setAddressSortOrder(orderedAddrs[i], static_cast(i)); + settings_->save(); +} + bool App::isMiningAddress(const std::string& addr) const { return settings_ && settings_->isMiningAddress(addr); diff --git a/src/ui/windows/balance_address_list.cpp b/src/ui/windows/balance_address_list.cpp index c10c1f1..818c2e6 100644 --- a/src/ui/windows/balance_address_list.cpp +++ b/src/ui/windows/balance_address_list.cpp @@ -41,7 +41,8 @@ std::vector BuildAddressListRows(const std::vectorbalance < 1e-9 && !input.hidden && !input.favorite) continue; + // Keep mining-flagged addresses visible even at 0 balance (they receive payouts). + if (hideZeroBalances && input.info->balance < 1e-9 && !input.hidden && !input.favorite && !input.mining) continue; rows.push_back({input.info, input.isZ, input.hidden, input.favorite, input.mining, input.label, input.icon, input.sortOrder}); } diff --git a/src/ui/windows/balance_components.cpp b/src/ui/windows/balance_components.cpp index b773644..3bd0565 100644 --- a/src/ui/windows/balance_components.cpp +++ b/src/ui/windows/balance_components.cpp @@ -343,12 +343,20 @@ void RenderSharedAddressList(App* app, float listH, float availW, for (int i = 0; i < (int)rows.size(); ++i) { if (mousePos.y > rowY[i] + rowH * 0.5f) insertIdx = i + 1; } - if (insertIdx != s_dragIdx && insertIdx != s_dragIdx + 1) { - int targetIdx = (insertIdx > s_dragIdx) ? insertIdx - 1 : insertIdx; - if (targetIdx >= 0 && targetIdx < (int)rows.size() && s_dragIdx < (int)rows.size()) { - app->swapAddressOrder(rows[s_dragIdx].info->address, - rows[targetIdx].info->address); - } + if (insertIdx != s_dragIdx && insertIdx != s_dragIdx + 1 && + s_dragIdx < (int)rows.size()) { + // Build the new visible order (move the dragged row to insertIdx) and + // persist dense sort orders 0..N-1 in one shot. This works even when the + // rows were still at the default order (a pairwise swap would no-op). + std::vector newOrder; + newOrder.reserve(rows.size()); + for (int i = 0; i < (int)rows.size(); ++i) + if (i != s_dragIdx) newOrder.push_back(rows[i].info->address); + int clamped = std::min(insertIdx, (int)rows.size()); + int destIdx = (insertIdx > s_dragIdx) ? clamped - 1 : clamped; + destIdx = std::max(0, std::min(destIdx, (int)newOrder.size())); + newOrder.insert(newOrder.begin() + destIdx, rows[s_dragIdx].info->address); + app->reorderAddresses(newOrder); } } } diff --git a/tests/test_phase4.cpp b/tests/test_phase4.cpp index 423b29c..8c6d04e 100644 --- a/tests/test_phase4.cpp +++ b/tests/test_phase4.cpp @@ -2491,6 +2491,27 @@ void testBalanceAddressListModel() EXPECT_EQ(rows.size(), static_cast(1)); EXPECT_EQ(rows[0].info->address, std::string("zs-high")); + // Mining-flagged addresses stay visible at 0 balance even with hide-zero on. + dragonx::AddressInfo tMine; + tMine.address = "R-miner"; tMine.label = "Payout"; tMine.balance = 0.0; tMine.type = "transparent"; + std::vector mineInputs = { + {&addresses[0], true, false, false, false, "Vault", "", -1}, // funded + {&tMine, false, false, false, /*mining=*/true, "Payout", "", -1}, // 0-balance mining + }; + auto mineRows = dragonx::ui::BuildAddressListRows(mineInputs, "", /*hideZero=*/true, false); + EXPECT_EQ(mineRows.size(), static_cast(2)); + bool sawMiner = false; + for (const auto& r : mineRows) if (r.info->address == std::string("R-miner")) sawMiner = true; + EXPECT_TRUE(sawMiner); + + // Explicit sort order overrides starred: a non-favorite ordered row outranks a favorite. + std::vector ordInputs = { + {&addresses[0], true, false, false, false, "Vault", "", 0}, // ordered (0) + {&addresses[2], false, false, true, false, "Favorite", "", -1}, // favorite, unordered + }; + auto ordRows = dragonx::ui::BuildAddressListRows(ordInputs, "", false, false); + EXPECT_EQ(ordRows[0].info->address, std::string("zs-high")); + auto layout = dragonx::ui::ComputeAddressRowLayout(10.0f, 20.0f, 300.0f, 50.0f, 12.0f, 16.0f, 6.0f, 4.0f, 2.0f); EXPECT_NEAR(layout.contentStartX, 22.0, 0.0001);