Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/CODING_STANDARDS.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ fn foo(msg: &crate::ui::DisputeChatMessage) { /* ... */ }
- **Extract helpers**: Break complex functions into smaller, focused helpers.
- **Use private functions**: Create internal helper functions when needed.

**Example**: If `handle_key_event` grows too large, split it into `handle_navigation_keys`, `handle_form_input`, etc.
**Example**: If `handle_key_event` grows too large, split it into `handle_navigation_keys`, `handle_form_input`, etc. Form typing lives in **`src/ui/key_handler/form_input.rs`**; global keys like **`n`** (cancel) and **`c`** (copy) must not swallow characters when **`is_creating_order_text_input`** is true — add guarded arms in `mod.rs` before the generic `Char(_)` handler.

### 5. Module and Function Organization

Expand Down
1 change: 1 addition & 0 deletions docs/DATABASE.md
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ The `orders` table is essential for:

- **Trade Keys**: Stored as hex-encoded secret keys. **Critical security data** - these keys are needed to decrypt messages for each trade.
- **Order Updates**: Orders are updated (not just inserted) when status changes, using upsert logic. Status writes are guarded for monotonic progression where applicable (stale/out-of-order DMs should not move an order backward in the trade phase graph); see `should_apply_status_transition` in `src/util/order_utils/helper.rs` and **DM_LISTENER_FLOW.md**.
- **Relay terminal reconcile**: `src/util/order_utils/relay_order_db_reconcile.rs` can set **`orders.status`** from the latest Mostro nostr order event when the relay reports a **terminal** status and the local row is still non-terminal. Runs on startup and on the periodic orders updater (`fetch_scheduler.rs`). Targeted mode (`Order::list_ids_for_targeted_relay_reconcile`) only considers rows with **`trade_keys`** set and status not in `TERMINAL_ORDER_HISTORY_STATUSES` (`helper.rs`). Does not replace trade-DM hydration for active phases; complements it when the client missed the final DM.
- **Maker/Taker persistence**: `save_order(..., is_maker)` sets `is_mine` from runtime flow (`true` for new-order flow, `false` for take-order flow).

**Source**: `src/models.rs:154`
Expand Down
3 changes: 3 additions & 0 deletions docs/DM_LISTENER_FLOW.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,9 @@ This function is where `OrderMessage` is created/updated and pushed into `messag

Key behaviors:

- **Early return for non-trade hydration actions**
`Action::NewOrder` and **`Action::CantDo`** return immediately (same as book-side noise). **`CantDo`** is an error response from Mostro (`Payload::CantDo`); it is handled on the **waiter** path (`order_utils/helper.rs` → user-facing `OperationResult`) and must **not** upsert SQLite or replace the per-order Messages row. Treating `CantDo` like a normal trade DM caused bogus order hydration after failed takes or invalid actions.

- **DB refresh/upsert for certain actions**
For `add-invoice`, `pay-invoice`, and **`pay-bond-invoice`** (Mostro Phase 1.5+ anti-abuse bond) where the payload embeds an order, the listener persists/upserts the order row (including request id when available). `pay-bond-invoice` is additionally allow-listed for null `request_id` DMs (`src/util/order_utils/helper.rs`) since Mostro emits these unsolicited after a take.

Expand Down
24 changes: 23 additions & 1 deletion docs/MESSAGE_FLOW_AND_PROTOCOL.md
Original file line number Diff line number Diff line change
Expand Up @@ -488,12 +488,34 @@ After a successful trade, Mostro may prompt with a DM whose **`action`** is **`r
- **UI**: **`UiMode::RatingOrder`** (`src/ui/app_state.rs`) — star row **1–5** (`mostro_core::MIN_RATING` / `MAX_RATING`), **Left/Right** or **+/-** to adjust, **Enter** to submit, **Esc** to dismiss. Rendered in **`src/ui/tabs/tab_content.rs`** (`render_rating_order`), opened from Messages **Enter** when the selected message’s action is **`Rate`** (`src/ui/key_handler/enter_handlers.rs`).
- **Send path**: **`execute_rate_user`** in **`src/util/order_utils/execute_send_msg.rs`** builds **`Message::new_order`** with **`Action::RateUser`**, **`Payload::RatingUser(rating)`**, and the trade **`order_id`**; **identity + trade keys** and **`send_dm` / `wait_for_dm`** match other trade messages. The response is expected to be **`Action::RateReceived`**. No counterparty pubkey is sent — Mostro resolves the peer server-side.

## Relay → SQLite order status reconcile

Mostrix can align local **`orders.status`** with **terminal** states published on Mostro nostr order events when trade DMs were missed or the client was offline.

**Source**: `src/util/order_utils/relay_order_db_reconcile.rs`, wired from `src/util/order_utils/fetch_scheduler.rs` and startup in `src/main.rs`.

| Path | When | What |
|------|------|------|
| **Bulk** | Orders updater tick (~30s) + **startup** | `fetch_mostro_order_events` → `aggregate_latest_orders_by_id` → `reconcile_terminal_order_statuses_from_relay` |
| **Targeted** | Same tick + **startup** | `Order::list_ids_for_targeted_relay_reconcile` (non-terminal rows with `trade_keys`) → round-robin up to **`TARGETED_RELAY_RECONCILE_MAX_PER_TICK`** (5) per-order fetches → `reconcile_one_order_if_terminal` |

`reconcile_one_order_if_terminal` only writes when the relay snapshot status is **terminal** (`is_terminal_trade_status`) and passes **`should_apply_status_transition`** (same monotonic rules as DM updates). Pending orders on the book are not “healed” from relay unless the relay reports a terminal outcome (e.g. **Expired**).

## Messages tab: trade timeline stepper (buy and sell listings)

The Messages detail panel shows a **six-step** timeline for trades with known **`order_kind`**. The highlighted column comes from **`message_trade_timeline_step`** → **`FlowStep`** (`src/ui/orders.rs`): **`BuyFlowStep(StepLabelsBuy)`** or **`SellFlowStep(StepLabelsSell)`**, each with discriminants **1…6** for UI columns (sell swaps the first two phase columns vs buy). Resolution dispatches to **`buy_listing_flow_step`** or **`sell_listing_flow_step`**, combining **`OrderMessage::order_status`**, **`is_mine`** (maker/taker), and **`action`**, via **`listing_step_from_status(order_kind, status)`** (kind-specific status mapping) and kind-specific **`_flow_step_from_action`**. **`Action::Rate`** / **`RateReceived`** are handled before status so **`rate`** DMs without a full order payload still highlight the final step.
The Messages detail panel shows a **six-step** timeline for trades with known **`order_kind`**. The highlighted column comes from **`message_trade_timeline_step`** → **`FlowStep`** (`src/ui/orders.rs`): **`BuyFlowStep(StepLabelsBuy)`** or **`SellFlowStep(StepLabelsSell)`**. Step enums use **`repr(u8)`** discriminants passed to **`FlowStep::step_number()`** (UI columns are **1…6** in `message_flow_tab.rs`; discriminant **0** = no highlight).

Resolution dispatches to **`buy_listing_flow_step`** or **`sell_listing_flow_step`**, combining **`OrderMessage::order_status`**, **`is_mine`** (maker/taker), and **`action`**, via **`listing_step_from_status(order_kind, status)`** (kind-specific status mapping) and kind-specific **`_flow_step_from_action`**. **`Action::Rate`** / **`RateReceived`** are handled before status so **`rate`** DMs without a full order payload still highlight the final step.

- **`Status::Pending`** / **`Status::WaitingTakerBond`** → **`StepPendingOrder`** (discriminant **0**): stepper shows **no** green/current column (all gray) until payment/bond phases start.
- **`Status::Success`** → final column (**`StepRate`**, discriminant **6**); avoids snapping back to an older step when reboot replay delivers a pre-success DM after the trade completed.

Step **wording** (strings per column) lives in **`src/ui/constants.rs`** (`StepLabel`, buy/sell step arrays); **`listing_timeline_labels`** selects the array by kind and role.

**Sidebar / info popups**: `message_action_compact_label_for_message` maps status to short labels (**Pending order**, **Trade Completed**, …) so list text stays accurate after hydration.

**Success-before-DM placeholder**: `try_placeholder_order_message_from_success` builds one synthetic **`OrderMessage`** when `OperationResult::Success` lands before any DM row (My Trades sidebar); placeholder **`action`** is status-driven and never uses synthetic **`take-buy`** / **`take-sell`** (those break Messages **Enter**). Applied in `order_ch_mng.rs` via `insert_placeholder_order_message_if_needed`.

See **[buy order flow.md](buy%20order%20flow.md)** and **[sell order flow.md](sell%20order%20flow.md)** for product context and **[TUI_INTERFACE.md](TUI_INTERFACE.md)** for **`UiMode`** overlays.

## Error Handling Patterns
Expand Down
6 changes: 3 additions & 3 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ Index of architecture and feature guides for the Mostrix TUI client. The [root R
## Core runtime & data

- **Startup & Configuration**: [STARTUP_AND_CONFIG.md](STARTUP_AND_CONFIG.md) — Boot sequence, settings, background tasks, DM router wiring, reconnect
- **DM listener & router**: [DM_LISTENER_FLOW.md](DM_LISTENER_FLOW.md) — `listen_for_order_messages`, TrackOrder vs waiter, startup `fetch_events` replay, in-memory `OrderMessage` list
- **DM listener & router**: [DM_LISTENER_FLOW.md](DM_LISTENER_FLOW.md) — `listen_for_order_messages`, TrackOrder vs waiter, startup `fetch_events` replay, in-memory `OrderMessage` list; **`Action::CantDo`** ignored in `handle_trade_dm_for_order` (errors use waiter / `OperationResult`, not Messages upserts)
- **Message Flow & Protocol**: [MESSAGE_FLOW_AND_PROTOCOL.md](MESSAGE_FLOW_AND_PROTOCOL.md) — How Mostrix talks to Mostro over Nostr (orders, GiftWrap, restarts, cooperative cancel / `TradeClosed`)
- **PoW & outbound events**: [POW_AND_OUTBOUND_EVENTS.md](POW_AND_OUTBOUND_EVENTS.md) — Instance `pow` (kind 38385), `nostr_pow_from_instance`, Gift Wrap outer mining (`gift_wrap_from_seal_with_pow`)
- **Database**: [DATABASE.md](DATABASE.md) — SQLite schema, `orders` / `users` / `admin_disputes`, migrations
- **Database**: [DATABASE.md](DATABASE.md) — SQLite schema, `orders` / `users` / `admin_disputes`, migrations; **relay → SQLite reconcile** for terminal order statuses (`relay_order_db_reconcile.rs`)
- **Key Management**: [KEY_MANAGEMENT.md](KEY_MANAGEMENT.md) — Deterministic derivation (NIP-06 path), identity vs trade keys

## UI & order flows

- **TUI Interface**: [TUI_INTERFACE.md](TUI_INTERFACE.md) — Navigation, modes, state; My Trades (static `order_chat_static` header vs `build_active_order_chat_list` live fields)
- **TUI Interface**: [TUI_INTERFACE.md](TUI_INTERFACE.md) — Navigation, modes, state; create-order form input; My Trades (static `order_chat_static` header vs `build_active_order_chat_list` live fields); Messages timeline (`StepPendingOrder` = no highlighted column while `Pending` / `WaitingTakerBond`)
- **UI constants** (`src/ui/constants.rs`): Shared copy (footers, help, **`StepLabel`** for the Messages tab buy/sell timeline)
- **Buy order flow (spec)**: [buy order flow.md](buy%20order%20flow.md) — Phase 1.5+ optional **anti-abuse bond** (`PayBondInvoice` / `WaitingTakerBond`) included as phase 0 for the taker
- **Sell order flow (spec)**: [sell order flow.md](sell%20order%20flow.md) — Phase 1.5+ optional **anti-abuse bond** for the taker (buyer)
Expand Down
9 changes: 5 additions & 4 deletions docs/STARTUP_AND_CONFIG.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,14 +135,15 @@ Current startup behavior:
Several background tasks are spawned to keep the UI and data in sync:

1. **Order Refresh**: Periodically fetches pending orders from Mostro.
2. **Trade Message Listener**: Listens for new messages related to active orders.
3. **Network Status Monitor**:
2. **Relay order DB reconcile** (startup + ~30s orders updater): `run_relay_order_db_reconcile_once` (bulk terminal sync from nostr order events) and `run_targeted_relay_order_db_reconcile_tick` (round-robin per-order fetch for local non-terminal trades with keys). See `relay_order_db_reconcile.rs` and **MESSAGE_FLOW_AND_PROTOCOL.md** (Relay → SQLite section).
3. **Trade Message Listener**: Listens for new messages related to active orders.
4. **Network Status Monitor**:
- `spawn_network_status_monitor` runs every 5 seconds.
- Re-checks relay reachability from disk settings and emits `NetworkStatus::Offline/Online`.
- On `Offline`, startup overlay text indicates automatic retry.
- On `Online`, `main.rs` triggers `reload_runtime_session_after_reconnect(...)` to reconnect
and reload runtime background tasks.
4. **Admin Chat Scheduler** (shared-key model):
5. **Admin Chat Scheduler** (shared-key model):
- In the main event loop, when `user_role == Admin`, a 5-second interval triggers `spawn_admin_chat_fetch` (see `src/util/order_utils/fetch_scheduler.rs`).
- A **single-flight guard** (`CHAT_MESSAGES_SEMAPHORE`: `AtomicBool`) ensures only one admin chat fetch runs at a time; overlapping ticks skip spawning a new fetch until the current one completes.
- For each in-progress dispute, rebuilds per-party shared `Keys` from `buyer_shared_key_hex` / `seller_shared_key_hex` stored in the `admin_disputes` table.
Expand All @@ -154,7 +155,7 @@ Several background tasks are spawned to keep the UI and data in sync:

**Source**: `src/main.rs` (background task setup), `src/util/order_utils/fetch_scheduler.rs` (admin chat scheduler), `src/ui/helpers/startup.rs` (`apply_admin_chat_updates`)

5. **DM Router Wiring (trade messages)**:
6. **DM Router Wiring (trade messages)**:
- App channel creation includes `dm_subscription_tx` / `dm_subscription_rx`.
- `set_dm_router_cmd_tx(dm_subscription_tx.clone())` publishes the sender globally for `wait_for_dm` (returns `Result`; startup fails fast if the mutex is poisoned).
- Before spawning the listener, `hydrate_startup_active_order_dm_state` loads non-terminal orders from SQLite and returns `active_order_trade_indices` plus `order_last_seen_dm_ts` cursors; `main.rs` seeds the shared active-order map.
Expand Down
12 changes: 10 additions & 2 deletions docs/TUI_INTERFACE.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,9 @@ The `handle_key_event` function dispatches keys based on the current `UiMode`.

### Specialized Input

- **Forms**: Character input and Backspace are handled by `handle_char_input` and `handle_backspace` for fields in `FormState`.
- **Forms**: Character input and Backspace are handled by `form_input::handle_char_input` and `form_input::handle_backspace` for fields in `FormState` while `UiMode::UserMode(UserMode::CreatingOrder(_))`.
- **Create New Order** (`src/ui/order_form.rs`, `src/ui/orders.rs` — `FormState` / `FormField`): **Tab** / **Shift+Tab** cycle focus; **Space** toggles buy/sell on **Order Type** and single/range on **Fiat Amount**; **Enter** submits; **Esc** cancels.
- **Global shortcut guard**: `n` / `N` (cancel) and `c` / `C` (copy invoice / observer clear) are handled before the generic `Char(_)` arm in `key_handler/mod.rs`. When a **text** field is focused (`is_creating_order_text_input` in `form_input.rs` — any field except **Order Type**), those keys are routed to form typing instead (fixes payment method labels like **SEPA** / **Bizum**). Outside the form, `n` still drives confirmation cancel (`handle_cancel_key`); `c` still copies PayInvoice / PayBondInvoice invoices.
- **Invoices**: `handle_invoice_input` handles text entry for Lightning invoices, including support for bracketed paste mode.
- **Paste support**: The event loop now centralizes paste routing for active inputs and supports:
- `Event::Paste(...)` (bracketed paste)
Expand Down Expand Up @@ -228,6 +230,10 @@ Renders a table of pending orders from the Mostro network. Status and order kind

Displays a list of direct messages related to the user's trades. Messages are tracked as `read` or `unread`. The detail panel includes a **trade timeline stepper** (six columns): **`FlowStep`** from `src/ui/orders.rs` (`message_trade_timeline_step`), with per-column copy from **`src/ui/constants.rs`** (`listing_timeline_labels`). See [buy order flow.md](buy%20order%20flow.md) and [sell order flow.md](sell%20order%20flow.md).

- **Sidebar labels**: `message_action_compact_label_for_message` prefers **`order_status`** over raw **`action`** (e.g. **Pending order**, **Trade Completed**) so reboot replay does not show stale action text. Non-actionable rows opened with **Enter** use that compact label in an **`OperationResult::Info`** popup (`enter_handlers.rs`).
- **Pending / bond-pending timeline**: `Status::Pending` and `Status::WaitingTakerBond` map to **`StepPendingOrder`** (discriminant **0**). The stepper uses `step_number() == 0`, so **no column is highlighted** (all steps gray) until the trade advances — avoids falsely lighting step 1 while the order is still on the book or awaiting bond.
- **Post-success placeholder row**: when **`OperationResult::Success`** arrives before any DM row exists, `try_placeholder_order_message_from_success` (`orders.rs`) inserts one synthetic **`OrderMessage`** for My Trades / Messages (action from status: maker → `NewOrder`, taker → `PayBondInvoice` / `WaitingSellerToPay` / etc.; never synthetic `take-buy` / `take-sell`). **`main.rs`** also resyncs My Trades from DB after **`Success`**, not only after history delete.

- **Enter** on a row: opens an invoice popup, a confirmation popup, the **rating** overlay (`RatingOrder`) when the daemon sent **`action: rate`**, or an info line for other actions (`src/ui/key_handler/enter_handlers.rs`).
- **Rating overlay**: `render_rating_order` in `src/ui/tabs/tab_content.rs`; keys **Left/Right** or **+/-** adjust stars, **Enter** submits, **Esc** closes.
- **Invoice popups (`NewMessageNotification`)**:
Expand All @@ -249,7 +255,9 @@ Displays a list of direct messages related to the user's trades. Messages are tr

A stateful form for creating new orders. It supports both fixed amounts and fiat ranges.

**Source**: `src/ui/order_form.rs`
**Fields** (`FormField` in `src/ui/orders.rs`): Order Type (toggle), Currency, Amount (sats), Fiat Amount (+ optional max when range), Payment Method, Premium (%), Invoice (optional), Expiration (days).

**Source**: `src/ui/order_form.rs`, `src/ui/key_handler/form_input.rs`, `src/ui/key_handler/navigation.rs` (Tab focus)

### My Trades (Order In Progress) updates

Expand Down
Loading
Loading