Skip to content

feat: add Mostro instance info tab and currency-based order filtering#31

Merged
grunch merged 20 commits into
mainfrom
mostro-infos
Mar 15, 2026
Merged

feat: add Mostro instance info tab and currency-based order filtering#31
grunch merged 20 commits into
mainfrom
mostro-infos

Conversation

@arkanoider

@arkanoider arkanoider commented Mar 13, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Added a new “Mostro Info” tab, available in both User and Admin modes, that reads the Mostro instance status event (kind 38385) and displays:
    • Mostro daemon version, commit, order limits, expirations, fee, and PoW configuration.
    • Lightning node details (alias, pubkey, version, commit, chains, networks, URIs).
    • Fiat currencies accepted from the fiat_currencies_accepted tag, which are now the source of truth for available fiat currencies.
  • Introduced a MostroInstanceInfo model and helper functions to:
    • Parse instance info from Nostr tags.
    • Fetch the latest instance info from relays using the current Mostro pubkey.
    • Cache this data in AppState and surface it in the UI and status bar.
  • Wired up the status bar to show currencies from the instance info instead of local settings, falling back to All (from Mostro instance) when fiat_currencies_accepted is missing.
  • Implemented fiat currency filtering via settings.toml:
    • currencies is now treated as a filter: empty list = no filter (show all currencies), non-empty list = only show orders whose fiat_code is in that list.
    • The settings tab’s Add Currency Filter / Clear Currency Filters options now act as view filters over the instance-provided currencies.

Implementation details

  • Nostr + model

    • Added util/mostro_info.rs:
      • MostroInstanceInfo with optional fields for all relevant tags (mostro_version, mostro_commit_hash, min_order_amount, max_order_amount, expirations, fee, pow, LND details, fiat_currencies_accepted, chains, networks, URIs).
      • mostro_info_from_tags(tags: Tags) -> Result<MostroInstanceInfo> to parse the kind 38385 event as per the Mostro protocol docs (Mostro Instance Status).
      • fetch_mostro_instance_info(client: &Client, mostro_pubkey: PublicKey) -> Result<Option<MostroInstanceInfo>> using Client::fetch_events with kind 38385, author = mostro_pubkey, and identifier set to the Mostro pubkey.
    • Re-exported these helpers from util::mod.
  • App state & startup

    • Extended AppState with mostro_info: Option<MostroInstanceInfo>.
    • In main.rs, after Nostr client + Mostro pubkey initialization and before entering the main loop:
      • Fetches the latest instance info and stores it into app.mostro_info, logging on failure or missing events.
    • Status bar now builds the currencies line from app.mostro_info:
      • If fiat_currencies_accepted is non-empty, joins and prints it.
      • Otherwise prints All (from Mostro instance).
  • Mostro Info tab

    • Updated ui/navigation.rs:
      • Added MostroInfo to both UserTab and AdminTab, updated Display, indexing, count, prev, and next.
    • Added ui/tabs/mostro_info_tab.rs and wired it via ui/tabs/mod.rs and ui/draw.rs:
      • For both roles, Tab::…(MostroInfo) renders the same structured view.
      • If app.mostro_info is None, shows a clear “no info loaded” message.
      • If present, shows grouped sections: Mostro daemon, order limits, Lightning node, and fiat currencies (from fiat_currencies_accepted).
    • Updated ui/help_popup.rs to include a short help section for the Mostro Info tab.
  • Currency filter behavior

    • Kept currencies: Vec<String> in Settings but repurposed it as a filter:
      • In util/order_utils/fetch_scheduler.rs, the periodic orders task:
        • Reloads settings.toml on each tick.
        • If settings.currencies is empty, passes None to get_orders (no filter).
        • If non-empty, passes Some(currencies) so only orders whose fiat_code is in that list are returned.
    • README.md now documents:
      • currencies as an optional filter in settings.toml (empty = all currencies; non-empty = restrict to listed codes).
      • That available currencies themselves still come from the Mostro instance info, not from settings.toml.
    • docs/ADMIN_DISPUTES.md was updated to clarify:
      • Settings tab options for Add/Clear Currency Filter are used as view filters.
      • Currency Filter Management section reflects the new behavior (filters over instance-provided currencies, and the status bar currencies come from the instance status event).

Testing

  • cargo fmt --all
  • cargo clippy --all-targets --all-features
  • cargo test
  • cargo build

Summary by CodeRabbit

  • New Features

    • Added a "Mostro Info" tab showing daemon status, Lightning node details, accepted fiat currencies, limits, fees, PoW info, and Enter-to-refresh; status bar now shows instance currencies and user filters separately.
  • Changes

    • Fiat currencies are authoritative from the Mostro instance; local setting renamed to currencies_filters and acts as a view-only filter.
    • Admin UI: Mode switch plus expanded dispute workflows and finalization actions.
    • Increased order message polling frequency.
  • Documentation

    • Clarified currency-filter semantics; removed several internal command/PR guidance docs.

@coderabbitai

coderabbitai Bot commented Mar 13, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds a Mostro Instance Info feature (fetch/parse Nostr kind 38385 and expose it in a new UI tab), renames the settings key currenciescurrencies_filters (client-side view filter), updates status bar display, updates related handlers/fetch logic, and removes several .cursor/commands/* docs.

Changes

Cohort / File(s) Summary
Configuration & Settings
settings.toml, src/settings.rs
Rename config key/Settings field currenciescurrencies_filters; add serde(alias = "currencies") for compatibility; update Default.
Mostro Instance Info Core
src/util/mostro_info.rs, src/util/mod.rs
New module to parse/fetch Mostro instance status (kind 38385); adds MostroInstanceInfo, MOSTRO_INSTANCE_INFO_KIND, mostro_info_from_tags, and fetch_mostro_instance_info; re-exported from util.
Application Startup & State
src/main.rs, src/ui/app_state.rs
Fetch Mostro info at startup; store result in AppState.mostro_info; derive display strings for status bar.
UI: Tabs, Navigation & Rendering
src/ui/tabs/mostro_info_tab.rs, src/ui/tabs/mod.rs, src/ui/navigation.rs, src/ui/draw.rs, src/ui/help_popup.rs, src/ui/status.rs
Add MostroInfo tab for User/Admin, adjust navigation indices/cycling, add help text, render Mostro info tab, and change status rendering to combined paragraph including Mostro currencies + filters.
UI Key Handling
src/ui/key_handler/enter_handlers.rs, src/ui/key_handler/settings.rs
Enter handler can refresh Mostro info (fetch + update app state); settings handlers now read/write currencies_filters.
Fetch & Scheduler
src/util/order_utils/fetch_scheduler.rs
Switch order-fetch filter source to currencies_filters; treat empty list as no filter (show all).
DM Refresh Behavior
src/util/dm_utils/mod.rs
Reduce DM/order message refresh interval from 5s to 2s.
New UI Module
src/ui/tabs/mostro_info_tab.rs
Public render_mostro_info_tab(f, area, app) added; renders detailed daemon/lightning/fiat info with formatting helpers.
Docs & Deleted Command Docs
README.md, docs/ADMIN_DISPUTES.md, .cursor/commands/pull_request.md, .cursor/commands/build.md, .cursor/commands/update_docs.md
Update README/ADMIN_DISPUTES to clarify fiat filter semantics and Mostro Info; remove several .cursor/commands/* documentation files.

Sequence Diagram(s)

sequenceDiagram
    participant App as App Startup
    participant Fetch as fetch_mostro_instance_info
    participant Nostr as Nostr Network
    participant Parser as mostro_info_from_tags
    participant State as AppState
    participant UI as Status Bar / MostroInfo Tab

    App->>Fetch: request(kind=38385, author=mostro_pubkey)
    Fetch->>Nostr: query events (with timeout)
    Nostr-->>Fetch: event or none / error
    Fetch->>Parser: parse tags (if event)
    Parser-->>Fetch: MostroInstanceInfo
    Fetch-->>App: Ok(Some(info)) / Ok(None) / Err
    App->>State: set app.mostro_info
    App->>UI: render status ("💱 Currencies: <mostro> - Filters: <filters>")
    UI-->>App: user opens MostroInfo tab (renders details if available)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • AndreaDiazCorreia
  • grunch
  • Catrya

Poem

🐰 I hopped to fetch the Mostro sign,
tags jingled bright, parsed line by line.
Filters renamed, the daemon spoke,
coins and nodes in a tidy poke.
Carrots, tabs, and info — hop, rejoice! 🥕✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 78.26% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and specifically summarizes the two main changes: adding a new Mostro instance info tab and repurposing currency configuration for order filtering.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch mostro-infos
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@arkanoider arkanoider requested review from Catrya and grunch March 13, 2026 10:16

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
docs/ADMIN_DISPUTES.md (1)

839-863: ⚠️ Potential issue | 🟡 Minor

Documentation code examples are inconsistent with actual implementation.

The code examples reference s.currencies (lines 845, 857), but the actual implementation in src/ui/key_handler/settings.rs uses s.currencies_filters. This inconsistency will confuse readers.

📝 Update code examples to match implementation
 pub fn save_currency_to_settings(currency_string: &str) {
     save_settings_with(
         |s| {
             let currency_upper = currency_string.trim().to_uppercase();
-            if !s.currencies.contains(&currency_upper) {
-                s.currencies.push(currency_upper);
+            if !s.currencies_filters.contains(&currency_upper) {
+                s.currencies_filters.push(currency_upper);
             }
         },
         "Failed to save currency to settings",
         "Currency filter added to settings file",
     );
 }

 /// Clear all currency filters (sets currencies to empty vector)
 pub fn clear_currency_filters() {
     save_settings_with(
         |s| {
-            s.currencies.clear();
+            s.currencies_filters.clear();
         },
         "Failed to clear currency filters",
         "All currency filters cleared",
     );
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/ADMIN_DISPUTES.md` around lines 839 - 863, Update the documentation
examples so the field name matches the implementation: replace references to
s.currencies in the functions save_currency_to_settings and
clear_currency_filters with the actual struct field s.currencies_filters (or the
exact field name used in src/ui/key_handler/settings.rs), ensuring the logic
still pushes, checks, and clears that field and the log messages remain
unchanged.
🧹 Nitpick comments (4)
src/util/mostro_info.rs (1)

66-144: mostro_info_from_tags always returns Ok—consider returning MostroInstanceInfo directly.

The function signature returns Result<MostroInstanceInfo> but never returns an Err. Since all parsing failures are silently converted to None/empty collections (which is fine for optional fields), the Result wrapper adds no value and forces callers to handle a non-existent error case.

♻️ Simplify return type
-pub fn mostro_info_from_tags(tags: Tags) -> Result<MostroInstanceInfo> {
+pub fn mostro_info_from_tags(tags: Tags) -> MostroInstanceInfo {
     let mut info = MostroInstanceInfo::default();
     // ... parsing logic unchanged ...
-    Ok(info)
+    info
 }

Then update the caller in fetch_mostro_instance_info:

-    let info = mostro_info_from_tags(event.tags.clone())?;
+    let info = mostro_info_from_tags(event.tags.clone());
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/util/mostro_info.rs` around lines 66 - 144, The function
mostro_info_from_tags returns Result<MostroInstanceInfo> but never produces Err;
change its signature to return MostroInstanceInfo directly, remove the Ok(...)
wrapper at the end (return info), and update any callers (e.g.,
fetch_mostro_instance_info) to accept the direct MostroInstanceInfo return
instead of handling a Result; also remove any ? propagation or Result-related
imports tied to mostro_info_from_tags.
src/main.rs (2)

191-202: Consider adding background refresh for mostro_info on fetch failure.

Currently, mostro_info is fetched only once at startup. If the fetch fails (network issues, relay unavailable), the user must restart the application. Since start_fetch_scheduler already runs a periodic loop, you might extend it to retry fetching mostro_info on failure.

Would you like me to open an issue to track adding a background retry mechanism for mostro_info?

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main.rs` around lines 191 - 202, The startup fetch of mostro_info (via
fetch_mostro_instance_info) only runs once and on Err leaves app.mostro_info
unset; add a background retry to populate mostro_info when the initial fetch
fails by extending start_fetch_scheduler to periodically call
fetch_mostro_instance_info and set app.mostro_info on success (and log
failures), or spawn a dedicated async task that loops with a delay and retries
until app.mostro_info is Some. Locate usages of fetch_mostro_instance_info, the
app.mostro_info field, and the start_fetch_scheduler function to implement the
loop, ensuring you avoid spawning duplicate retry tasks and respect shutdown
signals already used by start_fetch_scheduler.

465-476: Fallback message may be ambiguous when fetch failed vs. instance accepting all currencies.

When app.mostro_info is None (fetch failed) or when fiat_currencies_accepted is empty (instance accepts all), the same message "All (from Mostro instance)" is shown. Consider distinguishing these cases to help users understand the app state.

💡 Distinguish fetch failure from "accepts all"
         // Mostro instance currencies string
-        let mostro_instance_currencies = match app.mostro_info.as_ref() {
-            Some(info) if !info.fiat_currencies_accepted.is_empty() => {
-                info.fiat_currencies_accepted.join(", ")
-            }
-            _ => "All (from Mostro instance)".to_string(),
-        };
+        let mostro_instance_currencies = match &app.mostro_info {
+            Some(info) if !info.fiat_currencies_accepted.is_empty() => {
+                info.fiat_currencies_accepted.join(", ")
+            }
+            Some(_) => "All (from Mostro instance)".to_string(),
+            None => "Unknown (instance info not loaded)".to_string(),
+        };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main.rs` around lines 465 - 476, The fallback string for
mostro_instance_currencies conflates app.mostro_info == None (fetch failed) with
an empty fiat_currencies_accepted (instance accepts all); update the match on
app.mostro_info so you return a distinct message for the None case (e.g.,
"Unknown (failed to fetch Mostro info)") and keep "All (Mostro instance accepts
all)" for Some(info) with empty fiat_currencies_accepted, referencing the
mostro_instance_currencies variable, app.mostro_info and
fiat_currencies_accepted to locate and change the logic.
src/ui/tabs/mostro_info_tab.rs (1)

36-46: Consider removing implementation detail from user-facing message.

The message "Switch to this tab after startup to refresh automatically, or implement a manual refresh key later" exposes an internal TODO to users. Consider simplifying to just indicate the state without implementation hints.

💡 Simplify user-facing message
         None => {
-            let message = Paragraph::new(Line::from(vec![
-                Span::raw("No Mostro instance info has been loaded yet."),
-                Span::raw(" "),
-                Span::styled(
-                    "Switch to this tab after startup to refresh automatically, or implement a manual refresh key later.",
-                    Style::default().add_modifier(Modifier::ITALIC),
-                ),
-            ]));
+            let message = Paragraph::new(Line::from(Span::raw(
+                "No Mostro instance info available. Info is fetched at startup.",
+            )));
             f.render_widget(message, chunks[1]);
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ui/tabs/mostro_info_tab.rs` around lines 36 - 46, The user-facing message
in the None branch of the Mostro info tab currently exposes an implementation
TODO via the styled span; update the Paragraph/Spans created in the None branch
(the variable message built with Paragraph::new, Line::from,
Span::raw/Span::styled) to remove the "or implement a manual refresh key later"
text and replace the styled span with a concise state-only message (e.g.,
indicate that no info is loaded and the tab will refresh after startup) so only
the user-visible state is shown; keep the UI structure (Paragraph::new,
f.render_widget(chunks[1])) unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.cursor/commands/pull_request.md:
- Line 23: Remove the accidental personal reminder line "Do not push pr until i
read the comment and reviewed it!" from the .cursor/commands/pull_request.md
content; edit the file to delete that sentence so the document contains only
repository-relevant instructions and commit the change with a clear message like
"remove personal reminder from pull_request.md".

In `@docs/ADMIN_DISPUTES.md`:
- Around line 829-831: The documentation references the old field name
`currencies`; update the "Clear Currency Filters" section (the bullet that
currently reads "Clears the `currencies` array in `settings.toml`") to use the
new field name `currencies_filters` instead, keeping the explanatory sub-bullets
(e.g., that an empty list means “no filter”) unchanged so the text now reads
that it clears the `currencies_filters` array in `settings.toml`.

In `@README.md`:
- Around line 103-106: The README explanation refers to the old field name
`currencies`; update the text to reference the actual setting
`currencies_filters` everywhere in that paragraph (including examples), so the
description and examples match the real configuration key `currencies_filters`.
- Around line 67-68: The README example uses the old key name `currencies`;
update the example to the new settings field `currencies_filters` to match
src/settings.rs and the PR summary—replace the line `currencies = ["VES", "ARS",
"USD"]` with `currencies_filters = ["VES", "ARS", "USD"]` so docs match the
actual config key.

In `@src/settings.rs`:
- Line 15: The field rename to currencies_filters breaks deserialization for
existing settings.toml; update the struct definition for the currencies_filters
field in src/settings.rs to accept the old name by adding a serde alias (e.g.,
apply #[serde(alias = "currencies")] to the currencies_filters field) so serde
will accept either key, or alternatively document the required manual migration
in the release notes if you intentionally want to force users to change their
files.

In `@src/util/mostro_info.rs`:
- Around line 146-152: Docstring for the function that fetches the latest Mostro
instance info (the comment above the filter block in src/util/mostro_info.rs)
claims the filter includes tags `y=mostro` and `z=info` but the actual filter
used by the function (the query built for author/kind/identifier/limit) does not
include these tags; either add the `y` and `z` tag constraints to the
query/filter logic in the function that constructs the event filter (so the
query enforces `y=mostro` and `z=info`), or simplify the docstring to remove
mention of `y=mostro` and `z=info` so it matches the current behavior (adjust
the docstring above the filter construction in mostro_info.rs to reflect
whichever option you choose).

---

Outside diff comments:
In `@docs/ADMIN_DISPUTES.md`:
- Around line 839-863: Update the documentation examples so the field name
matches the implementation: replace references to s.currencies in the functions
save_currency_to_settings and clear_currency_filters with the actual struct
field s.currencies_filters (or the exact field name used in
src/ui/key_handler/settings.rs), ensuring the logic still pushes, checks, and
clears that field and the log messages remain unchanged.

---

Nitpick comments:
In `@src/main.rs`:
- Around line 191-202: The startup fetch of mostro_info (via
fetch_mostro_instance_info) only runs once and on Err leaves app.mostro_info
unset; add a background retry to populate mostro_info when the initial fetch
fails by extending start_fetch_scheduler to periodically call
fetch_mostro_instance_info and set app.mostro_info on success (and log
failures), or spawn a dedicated async task that loops with a delay and retries
until app.mostro_info is Some. Locate usages of fetch_mostro_instance_info, the
app.mostro_info field, and the start_fetch_scheduler function to implement the
loop, ensuring you avoid spawning duplicate retry tasks and respect shutdown
signals already used by start_fetch_scheduler.
- Around line 465-476: The fallback string for mostro_instance_currencies
conflates app.mostro_info == None (fetch failed) with an empty
fiat_currencies_accepted (instance accepts all); update the match on
app.mostro_info so you return a distinct message for the None case (e.g.,
"Unknown (failed to fetch Mostro info)") and keep "All (Mostro instance accepts
all)" for Some(info) with empty fiat_currencies_accepted, referencing the
mostro_instance_currencies variable, app.mostro_info and
fiat_currencies_accepted to locate and change the logic.

In `@src/ui/tabs/mostro_info_tab.rs`:
- Around line 36-46: The user-facing message in the None branch of the Mostro
info tab currently exposes an implementation TODO via the styled span; update
the Paragraph/Spans created in the None branch (the variable message built with
Paragraph::new, Line::from, Span::raw/Span::styled) to remove the "or implement
a manual refresh key later" text and replace the styled span with a concise
state-only message (e.g., indicate that no info is loaded and the tab will
refresh after startup) so only the user-visible state is shown; keep the UI
structure (Paragraph::new, f.render_widget(chunks[1])) unchanged.

In `@src/util/mostro_info.rs`:
- Around line 66-144: The function mostro_info_from_tags returns
Result<MostroInstanceInfo> but never produces Err; change its signature to
return MostroInstanceInfo directly, remove the Ok(...) wrapper at the end
(return info), and update any callers (e.g., fetch_mostro_instance_info) to
accept the direct MostroInstanceInfo return instead of handling a Result; also
remove any ? propagation or Result-related imports tied to
mostro_info_from_tags.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 29a8d50c-6233-4383-8bca-f27fb340e372

📥 Commits

Reviewing files that changed from the base of the PR and between 6fdbfc8 and 7a2527a.

📒 Files selected for processing (17)
  • .cursor/commands/pull_request.md
  • README.md
  • docs/ADMIN_DISPUTES.md
  • settings.toml
  • src/main.rs
  • src/settings.rs
  • src/ui/app_state.rs
  • src/ui/draw.rs
  • src/ui/help_popup.rs
  • src/ui/key_handler/settings.rs
  • src/ui/navigation.rs
  • src/ui/tabs/mod.rs
  • src/ui/tabs/mostro_info_tab.rs
  • src/util/dm_utils/mod.rs
  • src/util/mod.rs
  • src/util/mostro_info.rs
  • src/util/order_utils/fetch_scheduler.rs

Comment thread .cursor/commands/pull_request.md Outdated
Comment thread docs/ADMIN_DISPUTES.md
Comment thread README.md Outdated
Comment thread README.md Outdated
Comment thread src/settings.rs Outdated
Comment thread src/util/mostro_info.rs Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/ui/tabs/mostro_info_tab.rs`:
- Around line 55-61: The paragraph rendering currently can cut off long fields
in narrow terminals; update the widget configuration used around
Paragraph::new(lines) so long text is wrapped (use Paragraph's wrap setting,
e.g., Wrap { trim: true }) and, if needed for vertical overflow, convert to a
scrollable view using a scroll state and render via f.render_widget with that
state instead of a plain Paragraph; adjust usage of Paragraph, f.render_widget,
area, and lines accordingly to ensure node pubkey, URIs and joined currency
lists are readable on narrow terminals.
- Around line 41-42: The empty-state guidance string in the view (the literal
"Switch to this tab after startup to refresh automatically, or implement a
manual refresh key later.") is misleading because the tab does not auto-refresh
on switch; update the message in the MostroInfoTab render code (where that
literal is defined) to accurately describe current behavior—e.g. say it displays
the current state and requires a manual refresh or explain how to refresh—or
alternatively implement the actual auto-refresh-on-tab-switch behavior in the
MostroInfoTab render/update handler if you prefer keeping the original copy.
- Around line 79-90: The expiration policy rendering is missing three fields
from MostroInstanceInfo; update the UI code in mostro_info_tab.rs where lines
are built (the block calling
push_opt_i64/push_opt_u64/push_opt_u32/push_opt_f64) to also push
hold_invoice_expiration_window, hold_invoice_cltv_delta, and
invoice_expiration_window using the appropriate push_opt_* helpers (e.g.,
push_opt_u64 for expiration-window fields and push_opt_u32 for cltv if that is
the type on MostroInstanceInfo), keeping the same label style like "Hold invoice
expiration window", "Hold invoice CLTV delta", and "Invoice expiration window"
so the full expiration policy is displayed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a14155ed-23c5-4ee9-a57c-2bf91ed28e83

📥 Commits

Reviewing files that changed from the base of the PR and between 7a2527a and 45bc3bb.

📒 Files selected for processing (1)
  • src/ui/tabs/mostro_info_tab.rs

Comment thread src/ui/tabs/mostro_info_tab.rs Outdated
Comment thread src/ui/tabs/mostro_info_tab.rs Outdated
Comment thread src/ui/tabs/mostro_info_tab.rs

@grunch grunch left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On cargo run I get:

Error: Error loading settings: Error deserializing settings.toml: missing configuration field "currencies_filters"

it must be backwards compatible

@arkanoider

Copy link
Copy Markdown
Collaborator Author

On cargo run I get:

Error: Error loading settings: Error deserializing settings.toml: missing configuration field "currencies_filters"

it must be backwards compatible

Now should be accepting old currencies parameter and currencies_filters.

Improve Mostro info tab with enter key allowing refresh of infos.

@arkanoider arkanoider requested a review from grunch March 13, 2026 14:34

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (2)
docs/ADMIN_DISPUTES.md (1)

822-864: ⚠️ Potential issue | 🟡 Minor

Use currencies_filters consistently in the lower docs and snippet.

This section still refers to the old currencies field, and the example code still uses s.currencies / s.currencies.clear(). Users copying this guidance will end up with docs that do not match src/settings.rs or src/ui/key_handler/settings.rs.

📝 Suggested doc fix
-Admins (and users) can configure **local currency filters** to focus on specific fiat currencies when viewing orders. The **set of available currencies** still comes from the Mostro **instance status** event’s `fiat_currencies_accepted` tag ([Mostro Instance Status](https://mostro.network/protocol/other_events.html#mostro-instance-status-1)), but the `currencies` field in `settings.toml` is used as a **filter** over those orders.
+Admins (and users) can configure **local currency filters** to focus on specific fiat currencies when viewing orders. The **set of available currencies** still comes from the Mostro **instance status** event’s `fiat_currencies_accepted` tag ([Mostro Instance Status](https://mostro.network/protocol/other_events.html#mostro-instance-status-1)), but the `currencies_filters` field in `settings.toml` is used as a **filter** over those orders.
@@
-            if !s.currencies.contains(&currency_upper) {
-                s.currencies.push(currency_upper);
+            if !s.currencies_filters.contains(&currency_upper) {
+                s.currencies_filters.push(currency_upper);
             }
@@
-            s.currencies.clear();
+            s.currencies_filters.clear();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/ADMIN_DISPUTES.md` around lines 822 - 864, The docs and example code
refer to the old settings field `currencies`; update them to consistently use
`currencies_filters` instead: in the snippet for save_currency_to_settings and
clear_currency_filters replace references to s.currencies with
s.currencies_filters (e.g., check/append to s.currencies_filters and call
s.currencies_filters.clear()), and update any prose in this section to mention
"currencies_filters" so it matches src/settings.rs and
src/ui/key_handler/settings.rs.
src/ui/tabs/mostro_info_tab.rs (1)

38-45: ⚠️ Potential issue | 🟡 Minor

Update the empty-state hint to mention the Enter refresh.

The tab now supports Enter-driven refresh, but this copy still tells users to restart or wait for a future keybinding. In the one state where users most need guidance, the UI points them away from the supported action.

✏️ Suggested copy
                 Span::styled(
-                    "This view shows the latest Mostro instance info fetched at startup; restart Mostrix to refresh or add a manual refresh keybinding in a future update.",
+                    "This view shows the latest cached Mostro instance info. Press Enter in this tab to refresh it from relays.",
                     Style::default().add_modifier(Modifier::ITALIC),
                 ),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ui/tabs/mostro_info_tab.rs` around lines 38 - 45, The empty-state text
built in the message variable (constructed via Paragraph::new -> Line::from with
Span::raw and Span::styled) is outdated; replace the first or styled Span text
to instruct users that they can press Enter to refresh instead of telling them
to restart or wait for a future keybinding, keeping the italic style for the
hint (update the Span::styled content accordingly).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/ADMIN_DISPUTES.md`:
- Around line 157-165: Update the documentation text to reflect that legacy
currency filters are actually active: state that the scheduler reloads the
currencies_filters on each tick and uses the `currencies` array from
`settings.toml` to filter visible orders (so these filters are not ignored), and
clarify interaction with the Mostro instance `fiat_currencies_accepted` tag and
behavior in Admin/User modes (replace claims that filters are ignored/deprecated
with accurate behavior and note legacy status only if still relevant); reference
the `currencies`, `currencies_filters`, `settings.toml`,
`fiat_currencies_accepted`, scheduler, Mostro and Mostrix symbols when updating
the two "Add Currency Filter" and "Clear Currency Filters" bullets.

In `@src/ui/key_handler/enter_handlers.rs`:
- Around line 411-440: The refresh handler currently reloads the Mostro pubkey
from disk (crate::settings::load_settings_from_disk) which can diverge from the
runtime pubkey; instead use the runtime's pubkey in ctx to keep behavior
consistent. Replace the load/parse block with using ctx.mostro_pubkey (or parse
ctx.mostro_pubkey if it is a String) and pass that parsed PublicKey into
fetch_mostro_instance_info along with ctx.client; remove the disk-read path and
error branch so the handler uses ctx.mostro_pubkey (or update ctx first when
settings change) before calling fetch_mostro_instance_info.
- Around line 442-458: The refresh_result match leaves app.mostro_info unchanged
on Ok(None) and Err(e), causing stale Mostro data to remain; update the Ok(None)
and Err(_) arms of the match that handles refresh_result to clear the cached
data by setting app.mostro_info = None before updating app.mode (i.e., in the
Ok(None) arm that currently sets
UiMode::OperationResult(OperationResult::Info(...)) and in the Err(e) arm that
sets OperationResult::Error(...)), so the Mostro Info tab and status bar no
longer show stale currencies/details for the current pubkey.
- Around line 434-440: The Enter handler currently blocks the UI by calling
tokio::task::block_in_place and awaiting fetch_mostro_instance_info directly;
instead, spawn the network fetch as a background task (use tokio::spawn or
ctx.runtime.spawn) that calls fetch_mostro_instance_info(&client,
parsed_pubkey).await and then sends the result back over the existing
order_result_tx channel (similar to the Observer flow around the Observer
handler). Remove the block_in_place usage and ensure the Enter handler only
enqueues the background task and returns immediately; handle updating AppState
when the receiver for order_result_tx processes the outcome.

---

Duplicate comments:
In `@docs/ADMIN_DISPUTES.md`:
- Around line 822-864: The docs and example code refer to the old settings field
`currencies`; update them to consistently use `currencies_filters` instead: in
the snippet for save_currency_to_settings and clear_currency_filters replace
references to s.currencies with s.currencies_filters (e.g., check/append to
s.currencies_filters and call s.currencies_filters.clear()), and update any
prose in this section to mention "currencies_filters" so it matches
src/settings.rs and src/ui/key_handler/settings.rs.

In `@src/ui/tabs/mostro_info_tab.rs`:
- Around line 38-45: The empty-state text built in the message variable
(constructed via Paragraph::new -> Line::from with Span::raw and Span::styled)
is outdated; replace the first or styled Span text to instruct users that they
can press Enter to refresh instead of telling them to restart or wait for a
future keybinding, keeping the italic style for the hint (update the
Span::styled content accordingly).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3feccb4c-7ccc-443a-bacf-a7b09a02e1ad

📥 Commits

Reviewing files that changed from the base of the PR and between ea5bd28 and a13e474.

📒 Files selected for processing (6)
  • README.md
  • docs/ADMIN_DISPUTES.md
  • src/settings.rs
  • src/ui/key_handler/enter_handlers.rs
  • src/ui/tabs/mostro_info_tab.rs
  • src/util/mostro_info.rs

Comment thread docs/ADMIN_DISPUTES.md Outdated
Comment thread src/ui/key_handler/enter_handlers.rs Outdated
Comment thread src/ui/key_handler/enter_handlers.rs Outdated
Comment thread src/ui/key_handler/enter_handlers.rs Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/ui/status.rs (1)

27-45: ⚠️ Potential issue | 🟡 Minor

Handle empty lines so notifications are still visible.

On Line 34, the indicator is only added when iterating the last existing line. If lines is empty, pending notifications are silently not rendered.

💡 Proposed fix
-    let mut styled_lines: Vec<Line> = Vec::new();
+    let mut styled_lines: Vec<Line> = Vec::new();
     for (idx, line) in lines.iter().enumerate() {
         let mut spans = vec![Span::styled(
             line.to_string(),
             Style::default().bg(BACKGROUND_COLOR).fg(PRIMARY_COLOR),
         )];
@@
         styled_lines.push(Line::from(spans));
     }
+
+    if lines.is_empty() && pending_notifications > 0 {
+        let indicator_text = format!("🔔 {} new notification(s)", pending_notifications);
+        let indicator_style = if blink_on {
+            Style::default()
+                .bg(BACKGROUND_COLOR)
+                .fg(Color::Yellow)
+                .add_modifier(ratatui::style::Modifier::BOLD)
+        } else {
+            Style::default().bg(BACKGROUND_COLOR).fg(PRIMARY_COLOR)
+        };
+        styled_lines.push(Line::from(vec![Span::styled(indicator_text, indicator_style)]));
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ui/status.rs` around lines 27 - 45, The current loop only appends the
notification indicator when iterating the last element, so if lines is empty the
indicator never renders; before the for (idx, line) in lines.iter().enumerate()
loop (or replace the loop body), add a special-case that checks if
lines.is_empty() and, when pending_notifications > 0, builds a spans Vec similar
to the existing one (create a primary Span::styled for an empty/placeholder line
and then push the indicator Span using the same blink_on logic and styles used
for indicator_style) so the notification is rendered even with no lines; keep
using the same symbols (lines, pending_notifications, blink_on, spans,
Span::styled, PRIMARY_COLOR, BACKGROUND_COLOR) so the styling logic remains
consistent.
♻️ Duplicate comments (1)
src/ui/tabs/mostro_info_tab.rs (1)

28-34: ⚠️ Potential issue | 🟡 Minor

Empty-state guidance text is misleading: Enter key refresh already works.

The message states "restart Mostrix to refresh or add a manual refresh keybinding in a future update," but pressing Enter in this tab already triggers a refresh (see handle_enter_normal_mode in enter_handlers.rs). The hint at lines 139-142 correctly mentions this, but it's only shown when info is loaded—users in the empty state won't see it.

Consider updating the empty-state message to mention pressing Enter:

💡 Suggested text update
             let message = Paragraph::new(Line::from(vec![
                 Span::raw("No Mostro instance info has been loaded yet."),
                 Span::raw(" "),
                 Span::styled(
-                    "This view shows the latest Mostro instance info fetched at startup; restart Mostrix to refresh or add a manual refresh keybinding in a future update.",
+                    "Press Enter to fetch Mostro instance info from relays.",
                     Style::default().add_modifier(Modifier::ITALIC),
                 ),
             ]))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ui/tabs/mostro_info_tab.rs` around lines 28 - 34, The empty-state text in
the Paragraph built in mostro_info_tab.rs currently tells users to "restart
Mostrix to refresh or add a manual refresh keybinding", which is incorrect
because pressing Enter already triggers a refresh via handle_enter_normal_mode
in enter_handlers.rs; update the Span::raw/Span::styled content inside the
Paragraph::new(...) (the spans that mention restart/refresh) to instead instruct
users to press Enter to refresh (matching the hint shown when info is loaded) so
the empty-state and loaded-state guidance are consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/ui/tabs/mostro_info_tab.rs`:
- Around line 18-37: The code builds a Layout with a single constraint and then
indexes chunks[1], causing an out-of-bounds panic; either change the render
target to chunks[0] in the None branch (replace f.render_widget(message,
chunks[1]) with chunks[0]) or update the Layout::new constraints array to
produce two slots (e.g., add a second Constraint) so chunks[1] exists—update the
code in the match over app.mostro_info where f.render_widget is called
accordingly.

---

Outside diff comments:
In `@src/ui/status.rs`:
- Around line 27-45: The current loop only appends the notification indicator
when iterating the last element, so if lines is empty the indicator never
renders; before the for (idx, line) in lines.iter().enumerate() loop (or replace
the loop body), add a special-case that checks if lines.is_empty() and, when
pending_notifications > 0, builds a spans Vec similar to the existing one
(create a primary Span::styled for an empty/placeholder line and then push the
indicator Span using the same blink_on logic and styles used for
indicator_style) so the notification is rendered even with no lines; keep using
the same symbols (lines, pending_notifications, blink_on, spans, Span::styled,
PRIMARY_COLOR, BACKGROUND_COLOR) so the styling logic remains consistent.

---

Duplicate comments:
In `@src/ui/tabs/mostro_info_tab.rs`:
- Around line 28-34: The empty-state text in the Paragraph built in
mostro_info_tab.rs currently tells users to "restart Mostrix to refresh or add a
manual refresh keybinding", which is incorrect because pressing Enter already
triggers a refresh via handle_enter_normal_mode in enter_handlers.rs; update the
Span::raw/Span::styled content inside the Paragraph::new(...) (the spans that
mention restart/refresh) to instead instruct users to press Enter to refresh
(matching the hint shown when info is loaded) so the empty-state and
loaded-state guidance are consistent.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e5e77ca3-f448-4e7d-8e51-9f830f27d513

📥 Commits

Reviewing files that changed from the base of the PR and between a13e474 and 3294b5d.

📒 Files selected for processing (2)
  • src/ui/status.rs
  • src/ui/tabs/mostro_info_tab.rs

Comment thread src/ui/tabs/mostro_info_tab.rs Outdated

@mostronatorcoder mostronatorcoder Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

This PR adds a new "Mostro Info" tab that reads Mostro instance status events (kind 38385) and displays daemon version, Lightning node details, and accepted fiat currencies. The feature introduces network-dependent configuration that replaces the static currencies field.


🔴 Critical Issues

1. .cursor/ tracking inconsistency

Problem:

.gitignore:
+.cursor/

But commits track .cursor/commands/:
+.cursor/commands/pull_request.md

What's broken:

  • .gitignore declares .cursor/ should be ignored
  • Commit 6386e7d reverts the removal of .cursor/ tracking
  • Commit c8e6202 modifies .cursor/commands/pull_request.md
  • The .gitignore rule doesn't work because these files are already tracked

Impact:

  • Merge conflicts for contributors using different editors
  • PR diffs contaminated with editor-specific config
  • Inconsistent behavior: some .cursor/ files ignored, others tracked

Fix required:
Either:

# Option A: Actually ignore it
git rm --cached -r .cursor/
git commit -m "chore: stop tracking .cursor/ editor files"

OR

# Option B: Track it intentionally
# Remove .cursor/ from .gitignore
git add .gitignore
git commit -m "chore: track .cursor/ as project documentation"

Pick one. Half-tracking it creates technical debt.


2. Race condition in pubkey change + refresh flow

Location: src/ui/key_handler/enter_handlers.rs:318-339

The code:

// If the selected button is YES, immediately refresh Mostro instance info
if selected_button {
    let client_clone = client.clone();
    let refresh_result = tokio::task::block_in_place(|| {
        tokio::runtime::Handle::current().block_on(async move {
            crate::util::fetch_mostro_instance_info_from_settings(&client_clone).await
        })
    });
    // ...
}

The assumption: "After saving a new pubkey, immediately fetching from settings will use the new pubkey."

What actually happens:

  1. User changes pubkey from AAA to BBB
  2. Code saves new pubkey (buffered disk write)
  3. Code calls fetch_mostro_instance_info_from_settings()
  4. That function calls load_settings_from_disk()
  5. If disk write hasn't flushed yet, reads OLD pubkey
  6. Fetches instance info for AAA instead of BBB

Why it's real:

  • Rust I/O is buffered by default
  • No explicit flush() or fsync() before the refresh call
  • load_settings_from_disk() in the refresh path reads from disk, not memory

User experience:

  • Change pubkey → press Enter → sees old instance info → confusion
  • "It didn't work" → presses Enter multiple times → more confusion

Fix:
Pass the pubkey directly instead of round-tripping through disk:

if selected_button {
    let client_clone = client.clone();
    // Parse the new pubkey from the input state
    let new_pubkey = match PublicKey::from_str(&input_value) {
        Ok(pk) => pk,
        Err(e) => {
            log::error!("Invalid pubkey after confirmation: {}", e);
            return;
        }
    };
    
    let refresh_result = tokio::task::block_in_place(|| {
        tokio::runtime::Handle::current().block_on(async move {
            fetch_mostro_instance_info(&client_clone, new_pubkey).await
        })
    });
    // ...
}

This eliminates the filesystem round-trip and guarantees you fetch for the new pubkey.


3. block_in_place() freezes the TUI on slow networks

Location: Multiple places in enter_handlers.rs

The code:

let refresh_result = tokio::task::block_in_place(|| {
    tokio::runtime::Handle::current().block_on(async move {
        fetch_mostro_instance_info(&client, mostro_pubkey).await
    })
});

What this does:

  • Blocks the current Tokio executor thread
  • Spawns a nested async block
  • Waits synchronously for network I/O

Why it's a problem:

  • If you're on a single-threaded Tokio runtime (common in TUI apps), this blocks the entire runtime
  • Nothing can progress while waiting for relays to respond
  • Called from a key handler — a hot path triggered by user input

User experience:

  • User presses Enter in Mostro Info tab
  • Entire UI freezes (can't navigate, no spinner, looks crashed)
  • Network request takes 5+ seconds on slow connection
  • User mashes keys → chaos when UI unfreezes

Fix:
Use tokio::spawn() with a message channel:

// In app_state.rs, add:
pub enum UiEvent {
    MostroInfoFetched(Option<MostroInstanceInfo>),
    // ... other events
}

// In enter_handlers.rs:
let client = ctx.client.clone();
let mostro_pubkey = /* ... */;
tokio::spawn(async move {
    match fetch_mostro_instance_info(&client, mostro_pubkey).await {
        Ok(info) => {
            // Send to UI via channel
            tx.send(UiEvent::MostroInfoFetched(info)).ok();
        }
        Err(e) => {
            log::error!("Failed to fetch Mostro info: {}", e);
        }
    }
});

// Show loading state immediately
app.mode = UiMode::OperationResult(OperationResult::Info(
    "Fetching Mostro instance info...".to_string()
));

Then poll the channel in the main event loop and update app.mostro_info when ready.

This keeps the UI responsive during network I/O.


🟡 High-Priority Issues

4. No validation of instance info freshness

Location: src/util/mostro_info.rs:164-180

The code:

let filter = Filter::new()
    .author(mostro_pubkey)
    .kind(nostr_sdk::Kind::Custom(MOSTRO_INSTANCE_INFO_KIND))
    .identifier(mostro_pubkey.to_string())
    .limit(1);

let events = client.fetch_events(filter, FETCH_EVENTS_TIMEOUT).await?;
let event = match events.iter().next() {
    Some(ev) => ev,
    None => return Ok(None),
};

let info = mostro_info_from_tags(event.tags.clone())?;
Ok(Some(info))

What you're not checking:

  • Event timestamp (could be from months/years ago)
  • Event signature validity (assumed correct)
  • Whether the data makes sense (e.g., empty currencies list when there should be values)

Why it matters:

  • If a Mostro instance publishes instance info once and never updates it, Mostrix shows stale data forever
  • User sees "Accepted currencies: USD, EUR, BTC" but instance actually dropped BTC support
  • User creates order in BTC → Mostro rejects it → user: "But Mostrix said it was accepted!"

Potential financial impact:

  • User creates order with wrong assumptions about fees (shows 0.3%, actually 0.6%)
  • Order executed at wrong fee → unexpected loss

Recommended fix:

Add timestamp validation:

pub fn is_instance_info_stale(event: &Event) -> bool {
    let age_seconds = Timestamp::now().as_u64().saturating_sub(event.created_at.as_u64());
    age_seconds > 604800 // 7 days
}

// In fetch_mostro_instance_info:
let event = match events.iter().next() {
    Some(ev) => ev,
    None => return Ok(None),
};

if is_instance_info_stale(event) {
    log::warn!(
        "Mostro instance info is stale (published {} seconds ago)",
        Timestamp::now().as_u64().saturating_sub(event.created_at.as_u64())
    );
}

let info = mostro_info_from_tags(event.tags.clone())?;
Ok(Some(info))

And show age in the UI:

// In mostro_info_tab.rs, add:
lines.push(Line::from(vec![
    Span::styled("Last updated: ", Style::default().add_modifier(Modifier::BOLD)),
    Span::raw(format_age(event.created_at)),
]));

if is_instance_info_stale(&event) {
    lines.push(Line::from(Span::styled(
        "⚠ This data is older than 7 days and may be outdated",
        Style::default().fg(Color::Yellow),
    )));
}

5. Settings field rename breaks user configs without clear migration path

Location: src/settings.rs:15-16

The change:

-pub currencies: Vec<String>,
+#[serde(alias = "currencies")]
+pub currencies_filters: Vec<String>,

What you're trying to do:

  • Rename currenciescurrencies_filters
  • Keep backward compatibility with #[serde(alias = "currencies")]

What you didn't consider:

  1. What if a user has BOTH fields in their config?

    currencies = ["USD", "EUR"]
    currencies_filters = ["ARS"]

    Serde will pick one arbitrarily (likely the last one). No warning, no error, silent behavior change.

  2. Users won't know to rename the field

    • README documents the new name
    • But doesn't say "if you upgrade, rename this field"
    • Existing users will have currencies = in their config
    • It'll work via alias, but they won't know the field is deprecated

Impact:

  • Confusion when users read new README (says currencies_filters) but their config has currencies
  • Silent behavior if both fields present
  • No migration path documented

Recommended fix:

Add a warning on startup if the old field is detected:

impl Settings {
    pub fn validate(&self) -> Result<()> {
        // Check if using old 'currencies' field via deserializer metadata
        // (This requires custom deserialize logic, or...)
        
        // Simpler approach: check if config file contains old field name
        let config_str = std::fs::read_to_string("settings.toml")?;
        if config_str.contains("currencies =") && !config_str.contains("currencies_filters =") {
            log::warn!(
                "Deprecated field 'currencies' detected in settings.toml.                  Please rename to 'currencies_filters' for clarity.                  Backward compatibility will be maintained but may be removed in future versions."
            );
        }
        Ok(())
    }
}

And document the migration in README:

### Upgrading from v0.X

**Breaking change:** The `currencies` field in `settings.toml` has been renamed to `currencies_filters` for clarity.

- Old configs still work (backward compatibility via field alias)
- **Recommended:** Manually rename `currencies =` to `currencies_filters =` in your `settings.toml`
- If you have both fields, `currencies_filters` takes precedence

Example migration:
```diff
- currencies = ["USD", "EUR"]
+ currencies_filters = ["USD", "EUR"]

---

## 🟢 Minor Issues

### 6. Missing test coverage for critical paths

**Current tests:** Only parsing helpers are tested (CSV splitting, number parsing)

**What's NOT tested:**
- Network fetch with timeout
- Malformed event handling
- Empty currencies list behavior
- Settings field alias backward compatibility
- UI state updates after fetch

**Recommended additional tests:**

```rust
#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn fetch_returns_none_when_no_event_exists() {
        // Mock client that returns empty events
        // Assert Ok(None)
    }

    #[tokio::test]
    async fn fetch_handles_network_timeout() {
        // Mock client that times out
        // Assert proper error handling
    }

    #[test]
    fn parse_handles_malformed_tags() {
        let tags = /* malformed tags */;
        let result = mostro_info_from_tags(tags);
        // Should not panic, should return reasonable defaults
    }

    #[test]
    fn settings_alias_loads_old_currencies_field() {
        let toml = r#"
            mostro_pubkey = "test"
            currencies = ["USD"]
        "#;
        let settings: Settings = toml::from_str(toml).unwrap();
        assert_eq!(settings.currencies_filters, vec!["USD"]);
    }
}

📋 Summary of Required Changes

Priority Issue Estimated Fix Time
🔴 Critical Fix .cursor/ tracking inconsistency 5 min
🔴 Critical Fix pubkey refresh race condition 30 min
🔴 Critical Replace block_in_place() with async spawn 1-2 hours
🟡 High Add instance info freshness validation 30 min
🟡 High Document settings migration path 15 min
🟢 Minor Add missing test coverage 1-2 hours

Total estimated work: 4-6 hours


Recommendation

⚠️ REQUEST CHANGES

Reasoning:

  1. The race condition (issue #2) can cause user-visible bugs immediately
  2. The block_in_place() freezes (issue #3) will make the app feel broken on slow networks
  3. No timestamp validation (issue #4) creates financial risk if instance info goes stale

The good parts:

  • The feature design is solid (reading instance info from events is correct)
  • The UI implementation is clean
  • The parsing logic is robust
  • You have SOME tests (parsing helpers)

Before merge:

  1. Fix the race condition (#2) — this is a bug
  2. Replace block_in_place() with async spawn (#3) — UX killer
  3. Add timestamp validation (#4) — risk mitigation
  4. Clean up .cursor/ tracking (#1) — technical debt
  5. Document migration path (#5) — user confusion prevention

After those fixes:APPROVE

The core implementation is good, but the async handling and data freshness checks need work before this is production-ready.


Additional Context

I verified the implementation by:

  • Reviewing all changed files
  • Tracing the async execution paths
  • Checking settings serialization behavior
  • Analyzing the UI state update flow

The main concerns are runtime behavior (race conditions, freezes) and data staleness, not the architectural design itself.

@arkanoider

Copy link
Copy Markdown
Collaborator Author

This PR adds a new "Mostro Info" tab that reads Mostro instance status events (kind 38385) and displays daemon version, Lightning node details, and accepted fiat currencies. The feature introduces network-dependent configuration that replaces the static currencies field.

Last commit fix point 1. and 2.

@arkanoider

Copy link
Copy Markdown
Collaborator Author

Mostronator, with new commits all 5 points should be fixed. Please review!

@mostronatorcoder mostronatorcoder Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

Reviewed all 15 commits. 5 out of 6 critical issues from previous review have been resolved. Test coverage partially added.

Verdict: ⚠️ REQUEST CHANGES — 1 new critical issue + 1 remaining issue from previous review.


✅ Fixed Issues (from previous review)

1. .cursor/ tracking inconsistency

Status: ✅ FIXED
Commit: 7dd11f2
Evidence: .cursor/commands/ files removed from PR.

2. Race condition in pubkey change flow

Status: ✅ FIXED
Commit: 7dd11f2
Evidence: Now uses PublicKey::from_str(&key_string) directly instead of disk round-trip:

let new_pubkey = match PublicKey::from_str(&key_string) {
    Ok(pk) => pk,
    Err(e) => { ... }
};
fetch_mostro_instance_info(&client, new_pubkey).await

No more settings.toml write → flush → read race.

3. block_in_place() freezes TUI

Status: ✅ FIXED
Commit: 8fb851a
Evidence: All block_in_place() calls replaced with tokio::spawn() + channel communication (mostro_info_tx). TUI no longer blocks on slow network.

4. No timestamp validation on instance info events

Status: ✅ FIXED
Commit: 7b17b78
Evidence: Added is_stale() method with 7-day threshold:

const INSTANCE_INFO_STALE_SECS: u64 = 604_800;

pub fn is_stale(&self) -> bool {
    self.last_updated
        .map(|t| {
            let age_seconds = Timestamp::now().as_u64().saturating_sub(t.as_u64());
            age_seconds > INSTANCE_INFO_STALE_SECS
        })
        .unwrap_or(true)
}

Financial risk mitigated.

5. Settings migration undocumented

Status: ✅ FIXED
Commit: 7b17b78
Evidence: README updated with comprehensive migration guide:

  • "Upgrading from v0.x" section
  • Example diff showing currenciescurrencies_filters
  • Serde alias support documented
  • Field explanations section

⚠️ Remaining Issues

6. Missing test coverage (PARTIAL)

Status: ⚠️ PARTIAL FIX
Commit: a142e9f

What was added:

  • ✅ Parsing tests (split_csv, parse_i64/u64/u32/f64)
  • ✅ Empty tags test
  • ✅ Malformed tags test
  • ✅ Valid roundtrip test

What is still missing:

// From commit a142e9f, lines 292-298:
// Fetch tests would require a mock or test double for nostr_sdk::Client
// (e.g. a trait + impl for production and a mock that returns empty events
// or simulates timeout). Not implemented here to avoid refactoring the public API.
// Intended behavior:
// - fetch_returns_none_when_no_event_exists: client returns []; assert Ok(None)
// - fetch_handles_network_timeout: client returns/timeouts with error; assert Err

Why this matters:

  • Network failures are common in production (relay down, timeout, malformed response)
  • Without tests, we have no coverage of error paths in fetch_mostro_instance_info()
  • Financial app → error handling is critical

Recommendation: Add integration tests with mock client or use wiremock crate for relay simulation.


🚨 NEW CRITICAL ISSUE

7. Backward Compatibility Broken by validate_currencies_config()

Location: src/settings.rs:57-100

Problem:
The code has contradictory backward compatibility logic:

  1. Serde alias allows old field:
#[serde(alias = "currencies")]
pub currencies_filters: Vec<String>,

This should make old configs work transparently.

  1. But validation rejects them:
if has_old && !has_new {
    anyhow::bail!(
        "Deprecated field 'currencies' in {}. Please rename to 'currencies_filters' ...",
        path_display
    );
}

Result: Users with old configs cannot upgrade without manual intervention, even though serde would handle it correctly.

Impact:

  • Breaking change for existing users
  • Forces manual config editing during upgrade
  • Contradicts the README claim: "Old configs still work (backward compatibility via field alias)."

Two options:

Option A: True Backward Compatibility (Recommended)

Remove the validate_currencies_config() check entirely. Let serde alias handle it:

- validate_currencies_config(&hidden_file)?;

Users can keep currencies = indefinitely. It "just works."

Option B: Strict Migration with Warning Period

Keep validation but downgrade to a warning instead of bail!():

if has_old && !has_new {
    eprintln!("⚠️  WARNING: 'currencies' is deprecated. Please rename to 'currencies_filters'.");
    eprintln!("   See README 'Upgrading from v0.x' for details.");
    eprintln!("   This warning will become an error in a future version.");
}

Then in a future release (e.g., v0.3.0), upgrade warning to error.

My recommendation: Option A. Serde alias already provides clean backward compatibility. Adding a validation layer that contradicts it creates unnecessary friction for users.


Validation

  • cargo fmt --check — passes
  • cargo clippy --all-targets --all-features -- -D warnings — passes
  • cargo test — passes (10 tests)
  • All previous critical issues resolved
  • ⚠️ New issue #7 (backward compatibility) must be fixed
  • ⚠️ Issue #6 (network test coverage) still incomplete

Summary Table

Issue Previous Review Current Status Commits
1. .cursor/ tracking ❌ FAIL ✅ FIXED 7dd11f2
2. Race condition ❌ FAIL ✅ FIXED 7dd11f2
3. TUI freeze (block_in_place) ❌ FAIL ✅ FIXED 8fb851a
4. Timestamp validation ❌ FAIL ✅ FIXED 7b17b78
5. Migration undocumented ❌ FAIL ✅ FIXED 7b17b78
6. Missing tests ❌ FAIL ⚠️ PARTIAL a142e9f
7. Backward compat broken (NEW) ❌ FAIL 7b17b78

Recommendation

Fix issue #7 before merge. Choose Option A (remove validation) or Option B (downgrade to warning).

Issue #6 (network tests) can be addressed in a follow-up PR if time-constrained, but should not be forgotten.

Estimated fix time: 5-10 minutes for issue #7.

@arkanoider

arkanoider commented Mar 15, 2026

Copy link
Copy Markdown
Collaborator Author

Hi Mostronator, I discussed with @grunch about point 7 and we agreed just to rename the new setting to currencies_filter which is more meaningful. Since mostrix is totally a new product, we are still building and user base is just composed by devs now, we decided to exit gracefully mostrix if user has set :

  • ONLY currencies field
  • BOTH currencies AND currencies_filter

in these cases we tell users to update to currencies_filter.

I will wait a feedback from @grunch about creating now all the mocking machinery for web testing, maybe we could delay that later to add other missing features to mostrix.

In the new commit I improved also the responsiveness of filtered orders when a filter is applied.

New commit below...

@mostronatorcoder mostronatorcoder Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

Reviewed new commits since last review (25713da, 8a6e188, 36c85f3).

Verdict: ⚠️ REQUEST CHANGES — 1 documentation issue.


✅ Previous Issue Resolved

Issue #7: Backward Compatibility Contradiction (FIXED)

Previous problem: Code had contradictory logic:

  • Serde alias allowed currenciescurrencies_filter
  • But validation rejected it with bail!()

Resolution:

  • Commit 8a6e188: Removed #[serde(alias = "currencies")]
  • Commit 36c85f3: Updated test to expect rejection
  • Now consistent: strict migration required (no backward compatibility)

Result: ✅ Contradiction resolved. Code now enforces manual migration.


🚨 NEW ISSUE

Documentation Contradicts Implementation

Location: README.md:130-132

Problem:

- Old configs still work (backward compatibility via field alias).

Reality:

  • Commit 8a6e188 removed the serde alias
  • Old configs do NOT work — they will fail validation and exit with error
  • This is breaking change, not backward compatible

Impact:

  • Users will be confused when README says "still work" but app exits with error
  • Misleading documentation

Fix required:

- - Old configs still work (backward compatibility via field alias).
- **Recommended:** Manually rename `currencies =` to `currencies_filter =` in your `~/.mostrix/settings.toml`.
- If you have both fields, `currencies_filter` takes precedence.
+ - **Required:** Manually rename `currencies =` to `currencies_filter =` in your `~/.mostrix/settings.toml` before running.
+ - On first run with old config, mostrix will exit with an error message and instructions.
+ - This is a breaking change — manual migration is mandatory.

Also update the migration example text:

Example migration:

```diff
- currencies = ["USD", "EUR"]
+ currencies_filter = ["USD", "EUR"]
-```
+```

+**Note:** Mostrix will not start with the old `currencies` field. You must rename it manually.

✅ Code Quality Review

Validation

  • cargo fmt --check — passes
  • cargo clippy --all-targets --all-features -- -D warnings — passes
  • cargo test — passes (10 tests)

New Features (Commit 25713da)

Currency filtering UX improvements:

  • ✅ Case-insensitive matching (to_uppercase())
  • ✅ Efficient filtering with HashSet
  • ✅ Empty filter = show all (correct default)
  • ✅ Orders tab now respects currencies_filter setting
  • ✅ Good implementation, no issues

Settings changes:

  • ✅ Field renamed currencies_filterscurrencies_filter (singular, more accurate)
  • ✅ Consistent across codebase (verified with grep)
  • ✅ Validation error messages updated

Summary of All Issues

Issue Status Action Required
1. .cursor/ tracking ✅ FIXED None
2. Race condition ✅ FIXED None
3. TUI freeze ✅ FIXED None
4. Timestamp validation ✅ FIXED None
5. Migration undocumented ✅ FIXED None
6. Missing tests ⚠️ PARTIAL Network tests still missing (can be follow-up)
7. Backward compat contradiction ✅ FIXED None
8. Documentation incorrect (NEW) ❌ FAIL Update README

Recommendation

Fix issue #8 (documentation) — estimated time: 2 minutes.

Change is trivial:

  1. Remove "backward compatibility" claim
  2. Change "Recommended" to "Required"
  3. Add note that app will exit with error on old config

After fix, this PR is ready to merge.


Code Changes Reviewed

Commits since last review:

  • 25713da - Currency filtering UX + tests ✅
  • 8a6e188 - Removed serde alias ✅
  • 36c85f3 - Fixed tests ✅

Files changed:

  • README.md — ❌ needs fix (backward compat claim)
  • src/settings.rs — ✅ good (alias removed, validation consistent)
  • src/ui/tabs/orders_tab.rs — ✅ good (case-insensitive filter)
  • src/ui/draw.rs — ✅ good (currency display logic)
  • Other files — ✅ reviewed, no issues

Final Note

All critical functional issues from previous reviews have been resolved. Only documentation needs correction to match reality.

Estimated fix time: 2 minutes
Blocker: No (documentation only)

@grunch grunch merged commit b793563 into main Mar 15, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants