From 90072ea70bb0da0d1a6aa984349b75079a6a28b6 Mon Sep 17 00:00:00 2001 From: Lee *!* Clagett Date: Thu, 12 Feb 2026 21:38:35 -0500 Subject: [PATCH 1/9] Update lwsf pin --- external/lwsf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/lwsf b/external/lwsf index 8b5ad20..7a8a364 160000 --- a/external/lwsf +++ b/external/lwsf @@ -1 +1 @@ -Subproject commit 8b5ad20db947d6f382a2f55dd0492372df8bfb6b +Subproject commit 7a8a364dc7f8efff42fe4eb0fe417350638a1573 From ba6cae01aebd78072c62aede127d47833a84cbe4 Mon Sep 17 00:00:00 2001 From: Lee *!* Clagett Date: Tue, 3 Mar 2026 11:09:01 -0500 Subject: [PATCH 2/9] Update lwsf pin to include memory leak fixes --- external/lwsf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/lwsf b/external/lwsf index 7a8a364..d4a6004 160000 --- a/external/lwsf +++ b/external/lwsf @@ -1 +1 @@ -Subproject commit 7a8a364dc7f8efff42fe4eb0fe417350638a1573 +Subproject commit d4a6004a6fcf7a1076291882a79082afc702a5be From 94d2931d1104d457f494499761d5dabe9289a273 Mon Sep 17 00:00:00 2001 From: Lee *!* Clagett Date: Mon, 9 Mar 2026 22:33:10 -0400 Subject: [PATCH 3/9] Updates to support 'real-time' tx info --- src/views/history.cpp | 165 ++++++++++++++++++++++++++++-------------- src/views/wallet.cpp | 2 +- 2 files changed, 112 insertions(+), 55 deletions(-) diff --git a/src/views/history.cpp b/src/views/history.cpp index 12f1b1c..1501723 100644 --- a/src/views/history.cpp +++ b/src/views/history.cpp @@ -26,6 +26,7 @@ // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#include #include #include #include @@ -60,6 +61,8 @@ namespace lwcli { namespace view Monero::TransactionHistory* const history_; Monero::TransactionInfo const* info_; std::string note_; + std::string hash_; + std::vector transfers_; ftxui::Component note_input_; const ftxui::Component cancel_; ftxui::Component ok_; @@ -67,14 +70,25 @@ namespace lwcli { namespace view ftxui::Element title_; ftxui::Elements timestamp_; ftxui::Elements payment_id_; + ftxui::Elements confirmation_; ftxui::Elements amount_; ftxui::Elements fee_; + ftxui::Elements block_height_; ftxui::Elements minors_; ftxui::Elements coinbase_; + bool failed_; + bool pending_; + + void refresh() + { + on_refresh(history_->transaction(hash_)); + } bool OnEvent(ftxui::Event event) override final { - if (event == ftxui::Event::CtrlQ) + if (event == event::refresh_wallet) + refresh(); + else if (event == ftxui::Event::CtrlQ) throw event::close{}; return container_->OnEvent(std::move(event)); } @@ -85,16 +99,16 @@ namespace lwcli { namespace view {ftxui::text(_("Description: ")), note_input_->Render()}, timestamp_, payment_id_, - {ftxui::text(_("Confirmations: ")), ftxui::text(std::to_string(info_->confirmations()))}, + confirmation_, amount_, fee_, - {ftxui::text(_("Block Height: ")), ftxui::text(std::to_string(info_->blockHeight()))}, + block_height_, minors_, coinbase_ }; const std::size_t base_size = grid.size(); - for (const auto& transfer : info_->transfers()) + for (const auto& transfer : transfers_) { const std::string address = transfer.address.empty() ? std::string{_(" to Unknown Address")} : _(" to ") + transfer.address; @@ -111,9 +125,9 @@ namespace lwcli { namespace view vertical.reserve(4); vertical.push_back(ftxui::hcenter(ftxui::hbox(cancel_->Render(), ok_->Render()))); - if (info_->isFailed()) + if (failed_) vertical.push_back(ftxui::inverted(ftxui::hcenter(ftxui::text(_("FAILED"))))); - else if (info_->isPending()) + else if (pending_) vertical.push_back(ftxui::inverted(ftxui::hcenter(ftxui::text(_("PENDING"))))); else vertical.push_back(ftxui::separator()); @@ -123,48 +137,22 @@ namespace lwcli { namespace view return ftxui::window(title_, ftxui::vbox(std::move(vertical))); } - public: - explicit tx_details(Monero::TransactionHistory* history, std::size_t index) - : history_(history), - info_(nullptr), - note_(), - note_input_(nullptr), - cancel_(ftxui::Button(_("Cancel"), [] () { throw event::close{}; }, ftxui::ButtonOption::Ascii())), - ok_(nullptr), - container_(), - title_(nullptr), - timestamp_(), - payment_id_(), - amount_(), - fee_(), - minors_(), - coinbase_() + void on_refresh(const Monero::TransactionInfo* info) { - if (!history_) - throw std::runtime_error{"unexpected nullptr"}; - - info_ = history->transaction(index); - if (!info_) - throw std::runtime_error{"unexpected nullptr"}; - - title_ = ftxui::text(_("Tx ") + info_->hash()); + if (!info) { - std::tm expanded{}; - const std::time_t timestamp = info_->timestamp(); - if (!gmtime_r(std::addressof(timestamp), std::addressof(expanded))) - throw std::runtime_error{"gmtime failure"}; - - char buf[100] = {0}; - if (sizeof(buf) <= std::strftime(buf, sizeof(buf), "%B %d %Y %I:%M:%S UTC", std::addressof(expanded))) - throw std::runtime_error{"strftime failed"}; - timestamp_ = {ftxui::text(_("Timestamp: ")), ftxui::text(buf)}; + note_ = "TX is no longer in history"; + return; } - payment_id_ = {ftxui::text(_("Payment ID: ")), ftxui::text(info_->paymentId())}; - amount_ = {ftxui::text(_("Amount: ")), ftxui::text(print_amount(*info_))}; - fee_ = {ftxui::text(_("Fee: ")), ftxui::text(lwsf::displayAmount(info_->fee()))}; + + payment_id_ = {ftxui::text(_("Payment ID: ")), ftxui::text(info->paymentId())}; + amount_ = {ftxui::text(_("Amount: ")), ftxui::text(print_amount(*info))}; + confirmation_ = {ftxui::text(_("Confirmations: ")), ftxui::text(std::to_string(info->confirmations()))}; + fee_ = {ftxui::text(_("Fee: ")), ftxui::text(lwsf::displayAmount(info->fee()))}; + block_height_ = {ftxui::text(_("Block Height: ")), ftxui::text(std::to_string(info->blockHeight()))}; { std::string minors; - for (const std::uint32_t minor : info_->subaddrIndex()) + for (const std::uint32_t minor : info->subaddrIndex()) { if (!minors.empty()) minors.append(", "); @@ -172,15 +160,70 @@ namespace lwcli { namespace view } minors_ = {ftxui::text(_("Subaddress Minor: ")), ftxui::text(std::move(minors))}; } - coinbase_ = {ftxui::text(_("Coinbase: ")), ftxui::text(info_->isCoinbase() ? _("Yes"): _("No"))}; - note_ = info_->description(); + { + std::tm expanded{}; + const std::time_t timestamp = info->timestamp(); + if (gmtime_r(std::addressof(timestamp), std::addressof(expanded))) + { + char buf[100] = {0}; + if (sizeof(buf) <= std::strftime(buf, sizeof(buf), "%B %d %Y %I:%M:%S UTC", std::addressof(expanded))) + throw std::runtime_error{"strftime failed"}; + timestamp_ = {ftxui::text(_("Timestamp: ")), ftxui::text(buf)}; + } + else + timestamp_ = {ftxui::text(_("Timestamp: ")), ftxui::text("gmtime failure")}; + } + + transfers_.clear(); + const auto& transfers = info->transfers(); + std::copy(transfers.begin(), transfers.end(), std::back_inserter(transfers_)); + failed_ = info->isFailed(); + pending_ = info->isPending(); + } + + public: + explicit tx_details(Monero::TransactionHistory* history, std::size_t index) + : history_(history), + note_(), + hash_(), + transfers_(), + note_input_(nullptr), + cancel_(ftxui::Button(_("Cancel"), [] () { throw event::close{}; }, ftxui::ButtonOption::Ascii())), + ok_(nullptr), + container_(), + title_(nullptr), + timestamp_(), + payment_id_(), + confirmation_(), + amount_(), + fee_(), + block_height_(), + minors_(), + coinbase_(), + failed_(false), + pending_(false) + { + if (!history_) + throw std::runtime_error{"unexpected nullptr"}; + + const Monero::TransactionInfo* info = history->transaction(index); + if (!info) + throw std::runtime_error{"unexpected nullptr"}; + + hash_ = info->hash(); + on_refresh(info); + + title_ = ftxui::text(_("Tx ") + info->hash()); + coinbase_ = {ftxui::text(_("Coinbase: ")), ftxui::text(info->isCoinbase() ? _("Yes"): _("No"))}; + + note_ = info->description(); auto options = ftxui::InputOption::Default(); options.cursor_position = note_.size(); note_input_ = ftxui::Input(¬e_, std::move(options)); ok_ = ftxui::Button(_("OK"), [this] () { - history_->setTxNote(info_->hash(), note_); + history_->setTxNote(hash_, note_); throw event::close{}; }, ftxui::ButtonOption::Ascii()); @@ -256,7 +299,17 @@ namespace lwcli { namespace view { try { - if (overlay_) + if (event == event::refresh_wallet) + { + Monero::TransactionHistory* tx_history = wallet_->history(); + if (!tx_history) + throw std::runtime_error{"unexpeted history nullptr"}; + tx_history->refresh(); + if (overlay_) + overlay_->OnEvent(std::move(event)); + return true; + } + else if (overlay_) { overlay_->OnEvent(std::move(event)); return true; @@ -285,7 +338,6 @@ namespace lwcli { namespace view throw std::runtime_error{"unexpected history nullptr"}; std::map, std::pair, std::greater<>> ordered; - tx_history->refresh(); const auto history = tx_history->getAll(); for (std::size_t i = 0; i < history.size(); ++i) { @@ -311,14 +363,19 @@ namespace lwcli { namespace view const int direction = tx->direction(); const std::uint64_t fee = tx->fee(); + char date[12] = {0}; std::tm expanded{}; const std::time_t timestamp = tx->timestamp(); - if (!gmtime_r(std::addressof(timestamp), std::addressof(expanded))) - throw std::runtime_error{"gmtime failure"}; - - char date[12] = {0}; - if (sizeof(date) - 1 != std::strftime(date, sizeof(date), "%Y/%m/%d ", std::addressof(expanded))) - throw std::runtime_error{"strftime failed"}; + if (gmtime_r(std::addressof(timestamp), std::addressof(expanded))) + { + if (sizeof(date) - 1 != std::strftime(date, sizeof(date), "%Y/%m/%d ", std::addressof(expanded))) + throw std::runtime_error{"strftime failed"}; + } + else + { + std::strncpy(date, "gmtime fail", sizeof(date)); + date[sizeof(date) - 1] = 0; + } std::string payment_id = tx->paymentId(); if (payment_id.size() == 16 && payment_id.find_first_not_of('0') == std::string::npos) diff --git a/src/views/wallet.cpp b/src/views/wallet.cpp index ec1214a..890aaf2 100644 --- a/src/views/wallet.cpp +++ b/src/views/wallet.cpp @@ -152,7 +152,7 @@ namespace lwcli { namespace view const bool has_overlay = bool(state_.overlay); if (event == event::refresh_wallet) - return true; + return history_->OnEvent(std::move(event)); else if (state_.overlay) state_.overlay->OnEvent(std::move(event)); else if (event == ftxui::Event::CtrlQ) From e99c0e2dc0202df083dd935e1c60842a7c9bad95 Mon Sep 17 00:00:00 2001 From: Lee *!* Clagett Date: Mon, 9 Mar 2026 22:53:56 -0400 Subject: [PATCH 4/9] Tx fee is fixed, do not update on refresh --- src/views/history.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/history.cpp b/src/views/history.cpp index 1501723..79087dc 100644 --- a/src/views/history.cpp +++ b/src/views/history.cpp @@ -148,7 +148,6 @@ namespace lwcli { namespace view payment_id_ = {ftxui::text(_("Payment ID: ")), ftxui::text(info->paymentId())}; amount_ = {ftxui::text(_("Amount: ")), ftxui::text(print_amount(*info))}; confirmation_ = {ftxui::text(_("Confirmations: ")), ftxui::text(std::to_string(info->confirmations()))}; - fee_ = {ftxui::text(_("Fee: ")), ftxui::text(lwsf::displayAmount(info->fee()))}; block_height_ = {ftxui::text(_("Block Height: ")), ftxui::text(std::to_string(info->blockHeight()))}; { std::string minors; @@ -215,6 +214,7 @@ namespace lwcli { namespace view on_refresh(info); title_ = ftxui::text(_("Tx ") + info->hash()); + fee_ = {ftxui::text(_("Fee: ")), ftxui::text(lwsf::displayAmount(info->fee()))}; coinbase_ = {ftxui::text(_("Coinbase: ")), ftxui::text(info->isCoinbase() ? _("Yes"): _("No"))}; note_ = info->description(); From 8665250bbd257b7585a0e472c9f20292d5260ba3 Mon Sep 17 00:00:00 2001 From: Lee *!* Clagett Date: Tue, 10 Mar 2026 05:27:09 -0400 Subject: [PATCH 5/9] Tx history/details now follow ftxui rules, and table always updates in real-time --- src/views/history.cpp | 215 +++++++++++++++++------------------------- 1 file changed, 86 insertions(+), 129 deletions(-) diff --git a/src/views/history.cpp b/src/views/history.cpp index 79087dc..20eae4c 100644 --- a/src/views/history.cpp +++ b/src/views/history.cpp @@ -67,27 +67,11 @@ namespace lwcli { namespace view const ftxui::Component cancel_; ftxui::Component ok_; ftxui::Component container_; - ftxui::Element title_; - ftxui::Elements timestamp_; - ftxui::Elements payment_id_; - ftxui::Elements confirmation_; - ftxui::Elements amount_; - ftxui::Elements fee_; - ftxui::Elements block_height_; - ftxui::Elements minors_; - ftxui::Elements coinbase_; - bool failed_; - bool pending_; - - void refresh() - { - on_refresh(history_->transaction(hash_)); - } bool OnEvent(ftxui::Event event) override final { if (event == event::refresh_wallet) - refresh(); + return on_refresh(history_->transaction(hash_)); else if (event == ftxui::Event::CtrlQ) throw event::close{}; return container_->OnEvent(std::move(event)); @@ -95,16 +79,52 @@ namespace lwcli { namespace view virtual ftxui::Element OnRender() override final { + ftxui::Element buttons = ftxui::hcenter(ftxui::hbox(cancel_->Render(), ok_->Render())); + if (!info_) + { + return ftxui::window( + ftxui::text(_("Tx ") + hash_), + ftxui::vbox(std::move(buttons), ftxui::separator(), ftxui::text("No longer available")) + ); + } + + ftxui::Elements minors; + { + std::string minors_text; + for (const std::uint32_t minor : info_->subaddrIndex()) + { + if (!minors.empty()) + minors_text.append(", "); + minors_text.append(std::to_string(minor)); + } + minors = {ftxui::text(_("Subaddress Minor: ")), ftxui::text(std::move(minors_text))}; + } + + ftxui::Elements timestamp; + { + std::tm expanded{}; + const std::time_t raw = info_->timestamp(); + if (gmtime_r(std::addressof(raw), std::addressof(expanded))) + { + char buf[100] = {0}; + if (sizeof(buf) <= std::strftime(buf, sizeof(buf), "%B %d %Y %I:%M:%S UTC", std::addressof(expanded))) + throw std::runtime_error{"strftime failed"}; + timestamp = {ftxui::text(_("Timestamp: ")), ftxui::text(buf)}; + } + else + timestamp = {ftxui::text(_("Timestamp: ")), ftxui::text("gmtime failure")}; + } + std::vector grid{ {ftxui::text(_("Description: ")), note_input_->Render()}, - timestamp_, - payment_id_, - confirmation_, - amount_, - fee_, - block_height_, - minors_, - coinbase_ + std::move(timestamp), + {ftxui::text(_("Payment ID: ")), ftxui::text(info_->paymentId())}, + {ftxui::text(_("Confirmations: ")), ftxui::text(std::to_string(info_->confirmations()))}, + {ftxui::text(_("Amount: ")), ftxui::text(print_amount(*info_))}, + {ftxui::text(_("Fee: ")), ftxui::text(lwsf::displayAmount(info_->fee()))}, + {ftxui::text(_("Block Height: ")), ftxui::text(std::to_string(info_->blockHeight()))}, + std::move(minors), + {ftxui::text(_("Coinbase: ")), ftxui::text(info_->isCoinbase() ? _("Yes"): _("No"))}, }; const std::size_t base_size = grid.size(); @@ -124,98 +144,48 @@ namespace lwcli { namespace view ftxui::Elements vertical; vertical.reserve(4); - vertical.push_back(ftxui::hcenter(ftxui::hbox(cancel_->Render(), ok_->Render()))); - if (failed_) + vertical.push_back(std::move(buttons)); + if (info_->isFailed()) vertical.push_back(ftxui::inverted(ftxui::hcenter(ftxui::text(_("FAILED"))))); - else if (pending_) + else if (info_->isPending()) vertical.push_back(ftxui::inverted(ftxui::hcenter(ftxui::text(_("PENDING"))))); else vertical.push_back(ftxui::separator()); vertical.push_back(ftxui::gridbox(std::move(grid))); - return ftxui::window(title_, ftxui::vbox(std::move(vertical))); + return ftxui::window(ftxui::text(_("Tx ") + hash_), ftxui::vbox(std::move(vertical))); } - void on_refresh(const Monero::TransactionInfo* info) + bool on_refresh(const Monero::TransactionInfo* info) { - if (!info) - { - note_ = "TX is no longer in history"; - return; - } - - payment_id_ = {ftxui::text(_("Payment ID: ")), ftxui::text(info->paymentId())}; - amount_ = {ftxui::text(_("Amount: ")), ftxui::text(print_amount(*info))}; - confirmation_ = {ftxui::text(_("Confirmations: ")), ftxui::text(std::to_string(info->confirmations()))}; - block_height_ = {ftxui::text(_("Block Height: ")), ftxui::text(std::to_string(info->blockHeight()))}; - { - std::string minors; - for (const std::uint32_t minor : info->subaddrIndex()) - { - if (!minors.empty()) - minors.append(", "); - minors.append(std::to_string(minor)); - } - minors_ = {ftxui::text(_("Subaddress Minor: ")), ftxui::text(std::move(minors))}; - } - - { - std::tm expanded{}; - const std::time_t timestamp = info->timestamp(); - if (gmtime_r(std::addressof(timestamp), std::addressof(expanded))) - { - char buf[100] = {0}; - if (sizeof(buf) <= std::strftime(buf, sizeof(buf), "%B %d %Y %I:%M:%S UTC", std::addressof(expanded))) - throw std::runtime_error{"strftime failed"}; - timestamp_ = {ftxui::text(_("Timestamp: ")), ftxui::text(buf)}; - } - else - timestamp_ = {ftxui::text(_("Timestamp: ")), ftxui::text("gmtime failure")}; - } - transfers_.clear(); + info_ = info; + if (!info_) + return true; + const auto& transfers = info->transfers(); std::copy(transfers.begin(), transfers.end(), std::back_inserter(transfers_)); - failed_ = info->isFailed(); - pending_ = info->isPending(); + return true; } public: - explicit tx_details(Monero::TransactionHistory* history, std::size_t index) + explicit tx_details(Monero::TransactionHistory* history, const Monero::TransactionInfo* info) : history_(history), + info_(nullptr), note_(), hash_(), transfers_(), note_input_(nullptr), cancel_(ftxui::Button(_("Cancel"), [] () { throw event::close{}; }, ftxui::ButtonOption::Ascii())), ok_(nullptr), - container_(), - title_(nullptr), - timestamp_(), - payment_id_(), - confirmation_(), - amount_(), - fee_(), - block_height_(), - minors_(), - coinbase_(), - failed_(false), - pending_(false) + container_() { if (!history_) throw std::runtime_error{"unexpected nullptr"}; - const Monero::TransactionInfo* info = history->transaction(index); - if (!info) - throw std::runtime_error{"unexpected nullptr"}; - - hash_ = info->hash(); on_refresh(info); - - title_ = ftxui::text(_("Tx ") + info->hash()); - fee_ = {ftxui::text(_("Fee: ")), ftxui::text(lwsf::displayAmount(info->fee()))}; - coinbase_ = {ftxui::text(_("Coinbase: ")), ftxui::text(info->isCoinbase() ? _("Yes"): _("No"))}; + hash_ = info->hash(); note_ = info->description(); auto options = ftxui::InputOption::Default(); @@ -239,10 +209,9 @@ namespace lwcli { namespace view const std::shared_ptr wallet_; ftxui::Component table_; ftxui::Component overlay_; - ftxui::Element table_cached_; //!< Does not redraw when child is enabled const std::string title1_; const std::string title2_; - std::unordered_map row_map_; + std::vector row_map_; const std::uint32_t account_; bool Focusable() const override final { return true; } @@ -265,7 +234,6 @@ namespace lwcli { namespace view wallet_(std::move(wallet)), table_(), overlay_(nullptr), - table_cached_(nullptr), title1_(_("Account #") + std::to_string(account) + " / "), title2_(" / " + wallet_->address(account, 0).substr(0, 20) + "..."), row_map_(), @@ -305,6 +273,23 @@ namespace lwcli { namespace view if (!tx_history) throw std::runtime_error{"unexpeted history nullptr"}; tx_history->refresh(); + + std::map, const Monero::TransactionInfo*, std::greater<>> ordered; + const auto history = tx_history->getAll(); + for (const Monero::TransactionInfo* tx : history) + { + if (!tx) + throw std::runtime_error{"unexpected tx_info nullptr"}; + + if (tx->subaddrAccount() == account_) + ordered.try_emplace({tx->blockHeight(), tx->hash()}, tx); + } + + row_map_.clear(); + row_map_.reserve(ordered.size()); + for (const auto& entry : ordered) + row_map_.push_back(entry.second); + if (overlay_) overlay_->OnEvent(std::move(event)); return true; @@ -333,32 +318,11 @@ namespace lwcli { namespace view std::vector> transaction_list() { - Monero::TransactionHistory* tx_history = wallet_->history(); - if (!tx_history) - throw std::runtime_error{"unexpected history nullptr"}; - - std::map, std::pair, std::greater<>> ordered; - const auto history = tx_history->getAll(); - for (std::size_t i = 0; i < history.size(); ++i) - { - const Monero::TransactionInfo* tx = history[i]; - if (!tx) - throw std::runtime_error{"unexpected tx_info nullptr"}; - - if (tx->subaddrAccount() == account_) - ordered.try_emplace({tx->blockHeight(), tx->hash()}, tx, i); - } - std::vector> rows; - rows.reserve(ordered.size() + 2); + rows.reserve(row_map_.size() + 2); - std::size_t i = 0; - row_map_.clear(); - for (const auto& entry : ordered) + for (const Monero::TransactionInfo* tx : row_map_) { - const Monero::TransactionInfo* tx = entry.second.first; - row_map_.emplace(i, entry.second.second); - const std::uint64_t amount = tx->amount(); const int direction = tx->direction(); const std::uint64_t fee = tx->fee(); @@ -403,8 +367,6 @@ namespace lwcli { namespace view lwsf::displayAmount(tx->fee()), tx->hash().substr(0, 16) + "..." }); - - ++i; } return rows; @@ -412,19 +374,14 @@ namespace lwcli { namespace view ftxui::Element OnRender() override final { - /* Do not redraw table when showing tx. the - history()->refresh() call can invalidate pointers */ + auto table = ftxui::vbox({ + get_title(), + ftxui::text(_("Balance: ") + lwsf::displayAmount(wallet_->balance(account_))), + table_->Render() | ftxui::vscroll_indicator | ftxui::yframe | ftxui::center | ftxui::flex + }); if (!overlay_) - { - auto table = table_->Render(); // compute balance first in callback - table_cached_ = ftxui::vbox({ - get_title(), - ftxui::text(_("Balance: ") + lwsf::displayAmount(wallet_->balance(account_))), - std::move(table) | ftxui::vscroll_indicator | ftxui::yframe | ftxui::center | ftxui::flex - }); - return table_cached_; - } - return ftxui::dbox(table_cached_, decorate::overlay(overlay_->Render())); + return table; + return ftxui::dbox(std::move(table), decorate::overlay(overlay_->Render())); } }; } // anonymous From 99252ac2e81b00336f5d23bf1a7453c5b5f7e721 Mon Sep 17 00:00:00 2001 From: Lee *!* Clagett Date: Tue, 10 Mar 2026 08:16:48 -0400 Subject: [PATCH 6/9] Fix account loading --- src/views/history.cpp | 48 ++++++++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/src/views/history.cpp b/src/views/history.cpp index 20eae4c..151b944 100644 --- a/src/views/history.cpp +++ b/src/views/history.cpp @@ -228,6 +228,30 @@ namespace lwcli { namespace view return ftxui::text(title1_ + wallet_->getSubaddressLabel(account_, 0) + title2_); } + void load_history() + { + Monero::TransactionHistory* tx_history = wallet_->history(); + if (!tx_history) + throw std::runtime_error{"unexpeted history nullptr"}; + tx_history->refresh(); + + std::map, const Monero::TransactionInfo*, std::greater<>> ordered; + const auto history = tx_history->getAll(); + for (const Monero::TransactionInfo* tx : history) + { + if (!tx) + throw std::runtime_error{"unexpected tx_info nullptr"}; + + if (tx->subaddrAccount() == account_) + ordered.try_emplace({tx->blockHeight(), tx->hash()}, tx); + } + + row_map_.clear(); + row_map_.reserve(ordered.size()); + for (const auto& entry : ordered) + row_map_.push_back(entry.second); + } + public: explicit history_(std::shared_ptr&& wallet, std::uint32_t account) : ftxui::ComponentBase(), @@ -242,6 +266,8 @@ namespace lwcli { namespace view if (!wallet_) throw std::invalid_argument{"lwcli::view::history given nullptr"}; + load_history(); + // perform transalation lookup once table_ = component::table( {_("Date"), _("Amount"), _("Payment ID"), _("Label"), _("Desription"), _("Block"), _("Fee"), _("Hash")}, @@ -269,27 +295,7 @@ namespace lwcli { namespace view { if (event == event::refresh_wallet) { - Monero::TransactionHistory* tx_history = wallet_->history(); - if (!tx_history) - throw std::runtime_error{"unexpeted history nullptr"}; - tx_history->refresh(); - - std::map, const Monero::TransactionInfo*, std::greater<>> ordered; - const auto history = tx_history->getAll(); - for (const Monero::TransactionInfo* tx : history) - { - if (!tx) - throw std::runtime_error{"unexpected tx_info nullptr"}; - - if (tx->subaddrAccount() == account_) - ordered.try_emplace({tx->blockHeight(), tx->hash()}, tx); - } - - row_map_.clear(); - row_map_.reserve(ordered.size()); - for (const auto& entry : ordered) - row_map_.push_back(entry.second); - + load_history(); if (overlay_) overlay_->OnEvent(std::move(event)); return true; From 5fc552411e518e191bc9a0ecf21c13becb0e26d1 Mon Sep 17 00:00:00 2001 From: Lee *!* Clagett Date: Tue, 10 Mar 2026 18:03:21 -0400 Subject: [PATCH 7/9] Fix table selection on details close --- src/components/table.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/table.cpp b/src/components/table.cpp index fb37e07..aa0c072 100644 --- a/src/components/table.cpp +++ b/src/components/table.cpp @@ -196,8 +196,6 @@ namespace lwcli { namespace component row.Decorate(ftxui::focus); } } - else - selected_ = -1; if (selected_ != highlighted_ && min_row() <= highlighted_ && highlighted_ < rows_) { From 00d1aea9ac7de85f331c05913e9bb9bb42c40e44 Mon Sep 17 00:00:00 2001 From: Lee *!* Clagett Date: Fri, 13 Mar 2026 21:11:37 -0400 Subject: [PATCH 8/9] Update lwsf pin --- external/lwsf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/lwsf b/external/lwsf index d4a6004..c416c37 160000 --- a/external/lwsf +++ b/external/lwsf @@ -1 +1 @@ -Subproject commit d4a6004a6fcf7a1076291882a79082afc702a5be +Subproject commit c416c3705675f174178e6fbca954351ab453055e From bb87de3e03fc4c777b2caefd2c367317342948d4 Mon Sep 17 00:00:00 2001 From: Lee *!* Clagett Date: Fri, 13 Mar 2026 21:15:27 -0400 Subject: [PATCH 9/9] Various bug and compilation fixes --- src/components/table.cpp | 1 + src/main.cpp | 4 +++- src/views/accounts.cpp | 1 + src/views/history.cpp | 37 +++++++++++++++++-------------------- src/views/wallet.cpp | 2 +- 5 files changed, 23 insertions(+), 22 deletions(-) diff --git a/src/components/table.cpp b/src/components/table.cpp index aa0c072..1dd3b11 100644 --- a/src/components/table.cpp +++ b/src/components/table.cpp @@ -26,6 +26,7 @@ // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#include #include #include #include diff --git a/src/main.cpp b/src/main.cpp index 969977b..5f2e9fb 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -26,6 +26,7 @@ // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#include #include #include #include @@ -289,7 +290,8 @@ int main(int, const char* argv[]) auto window = ftxui::CatchEvent(lwcli::view::manager(std::move(wm), std::move(prog.file)), [&] (ftxui::Event event) { - state.last_event = std::chrono::steady_clock::now().time_since_epoch().count(); + if (event != lwcli::event::refresh_wallet) + state.last_event = std::chrono::steady_clock::now().time_since_epoch().count(); if (event == ftxui::Event::CtrlC) { state.screen.ExitLoopClosure()(); diff --git a/src/views/accounts.cpp b/src/views/accounts.cpp index c80c06d..2a2a528 100644 --- a/src/views/accounts.cpp +++ b/src/views/accounts.cpp @@ -28,6 +28,7 @@ #include "wallet.h" +#include #include #include #include diff --git a/src/views/history.cpp b/src/views/history.cpp index 151b944..9c2e2fe 100644 --- a/src/views/history.cpp +++ b/src/views/history.cpp @@ -64,10 +64,12 @@ namespace lwcli { namespace view std::string hash_; std::vector transfers_; ftxui::Component note_input_; - const ftxui::Component cancel_; - ftxui::Component ok_; + ftxui::Component buttons_; ftxui::Component container_; + bool Focusable() const override final { return true; } + ftxui::Component ActiveChild() override final { return container_; } + bool OnEvent(ftxui::Event event) override final { if (event == event::refresh_wallet) @@ -79,7 +81,7 @@ namespace lwcli { namespace view virtual ftxui::Element OnRender() override final { - ftxui::Element buttons = ftxui::hcenter(ftxui::hbox(cancel_->Render(), ok_->Render())); + auto buttons = ftxui::hcenter(buttons_->Render()); if (!info_) { return ftxui::window( @@ -128,7 +130,7 @@ namespace lwcli { namespace view }; const std::size_t base_size = grid.size(); - for (const auto& transfer : transfers_) + for (const auto& transfer : info_->transfers()) { const std::string address = transfer.address.empty() ? std::string{_(" to Unknown Address")} : _(" to ") + transfer.address; @@ -159,13 +161,7 @@ namespace lwcli { namespace view bool on_refresh(const Monero::TransactionInfo* info) { - transfers_.clear(); info_ = info; - if (!info_) - return true; - - const auto& transfers = info->transfers(); - std::copy(transfers.begin(), transfers.end(), std::back_inserter(transfers_)); return true; } @@ -177,8 +173,7 @@ namespace lwcli { namespace view hash_(), transfers_(), note_input_(nullptr), - cancel_(ftxui::Button(_("Cancel"), [] () { throw event::close{}; }, ftxui::ButtonOption::Ascii())), - ok_(nullptr), + buttons_(), container_() { if (!history_) @@ -190,17 +185,19 @@ namespace lwcli { namespace view note_ = info->description(); auto options = ftxui::InputOption::Default(); options.cursor_position = note_.size(); + options.multiline = false; note_input_ = ftxui::Input(¬e_, std::move(options)); - ok_ = ftxui::Button(_("OK"), [this] () { - history_->setTxNote(hash_, note_); - throw event::close{}; - }, ftxui::ButtonOption::Ascii()); - - container_ = ftxui::Container::Vertical({ - ftxui::Container::Horizontal({cancel_, ok_}), - note_input_ + buttons_ = ftxui::Container::Horizontal({ + ftxui::Button(_("Cancel"), [] () { throw event::close{}; }, ftxui::ButtonOption::Ascii()), + ftxui::Button(_("OK"), [this] () { + history_->setTxNote(hash_, note_); + throw event::close{}; + }, ftxui::ButtonOption::Ascii()) }); + + container_ = ftxui::Container::Vertical({buttons_, note_input_}); + Add(container_); } }; diff --git a/src/views/wallet.cpp b/src/views/wallet.cpp index 890aaf2..1000abe 100644 --- a/src/views/wallet.cpp +++ b/src/views/wallet.cpp @@ -83,7 +83,7 @@ namespace lwcli { namespace view ftxui::Component ActiveChild() override final { if (state_.overlay) - return state_.overlay; + return state_.overlay; return ui_; }