Skip to content
Open
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/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Index of architecture and feature guides for the Mostrix TUI client. The [root R

## UI & order flows

- **TUI Interface**: [TUI_INTERFACE.md](TUI_INTERFACE.md) — Navigation, modes, state; create-order form input; **My Trades** (`user_my_trades_interactive`, scroll, receive attachments + Ctrl+S save, **Ctrl+O** send picker + **Ctrl+Shift+O** retry, `order_chat_static` vs live projection); Messages timeline (`StepPendingOrder` = no highlighted column while `Pending` / `WaitingTakerBond` / `WaitingMakerBond`)
- **TUI Interface**: [TUI_INTERFACE.md](TUI_INTERFACE.md) — Navigation, modes, state; **Create New Order** (sectioned form, live preview receipt, searchable currency picker from instance or `currencies.rs`, draft + leave guard, inline validation); **My Trades** (`user_my_trades_interactive`, scroll, receive attachments + Ctrl+S save, **Ctrl+O** send picker + **Ctrl+Shift+O** retry, `order_chat_static` vs live projection); Messages timeline (`StepPendingOrder` = no highlighted column while `Pending` / `WaitingTakerBond` / `WaitingMakerBond`)
- **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+ taker bond and Phase 5+ maker bond (`PayBondInvoice` / `WaitingTakerBond` / `WaitingMakerBond`)
- **Sell order flow (spec)**: [sell order flow.md](sell%20order%20flow.md) — Phase 1.5+ taker bond and Phase 5+ maker bond (`PayBondInvoice` / `WaitingTakerBond` / `WaitingMakerBond`)
Expand Down
90 changes: 80 additions & 10 deletions docs/TUI_INTERFACE.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ pub struct AppState {
pub active_order_trade_indices: Arc<Mutex<HashMap<uuid::Uuid, i64>>>,
pub selected_message_idx: usize,
pub pending_notifications: Arc<Mutex<usize>>,
pub order_form_draft: Option<FormState>, // preserved New Order draft when leaving tab (see Create New Order)
// …see source for observer and attachment fields…
}
```
Expand Down Expand Up @@ -77,7 +78,7 @@ Focused on trading and order management.
- **My Trades**: Manage active trades.
- **Messages**: Direct messages for trade coordination.
- **Settings**: Local configuration, including key rotation via **Generate New Keys** and mnemonic backup prompts. **User mode only**: **Set Lightning Address (buyer)** / **Clear Lightning Address** — optional `user@domain.com` stored in `settings.toml`; confirm-save fetches LNURL metadata (`payRequest`) before persisting (see `src/util/ln_address.rs`, `spawn_verify_and_save_ln_address_task`). The visible menu and **Enter** routing share **`ADMIN_SETTINGS`** / **`USER_SETTINGS`** in `src/ui/tabs/settings_tab.rs` (`SettingsMenuAction` + label per row; **`settings_action_for_index`**).
- **Create New Order**: Form for publishing new orders.
- **Create New Order**: Sectioned order form with live preview, searchable currency picker (instance `fiat_currencies_accepted` or bundled ISO list), draft persistence, and leave guard when switching tabs with unsaved input.

### Admin Role

Expand Down Expand Up @@ -146,6 +147,14 @@ The primary shared popup is the **operation result** modal, used for:

When the popup is closed (**Esc** or **Enter**) from the **Disputes in Progress** tab, the app stays on that tab and returns to **ManagingDispute** mode (it does not switch to the first tab).

**Create New Order leave guard** (`UserMode::ConfirmLeaveOrder`):

- **Trigger**: **Left** / **Right** tab navigation while editing a dirty `FormState` on the Create New Order tab.
- **Popup**: `order_confirm::render_leave_confirm` — "Leave this order draft?" with readiness hint (`missing_hint`) and note that the draft is preserved.
- **Buttons**: **Keep editing** (default, left) returns to `CreatingOrder`; **Leave** (right) stores `AppState.order_form_draft` and switches to the adjacent tab.
- **Close**: **Esc** returns to editing (same as Keep editing). **Enter** confirms the highlighted button.
- **Source**: `src/ui/key_handler/navigation.rs` (`handle_form_leave_attempt`), `src/ui/draw.rs` (overlay render + auto-restore draft on tab entry).

**Example**: Rendering the `OperationResult` popup.

**Source**: `src/ui/operation_result.rs` (rendering), `src/ui/orders.rs` (`OperationResult` enum). Notable variants handled in [`handle_operation_result`](../src/util/dm_utils/order_ch_mng.rs):
Expand Down Expand Up @@ -227,8 +236,14 @@ The `handle_key_event` function dispatches keys based on the current `UiMode`.
### Specialized Input

- **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.
- **Create New Order** (`src/ui/order_form.rs`, `src/ui/orders.rs` — `FormState` / `FormField`, `src/ui/currencies.rs` — picker metadata):
- **Layout**: Two-column body — **Order details** (left, sectioned **TRADE** / **PRICING** / **TERMS** with compact input strips) + **Live preview** receipt card (right); contextual **Field help** strip and footer key hints below.
- **Focus**: **Tab** / **Shift+Tab** cycle fields; focused row shows a `▸` accent and green input strip; inline **✓** / **✗** per field; section headers turn green when all fields in that section validate.
- **Toggles**: **Space** on **Order Type** toggles buy/sell (`⇄ Space` hint); **Space** on **Fiat Amount** toggles single/range; **Space** on **Payment Method** inserts a space (labels like `SEPA Instant` are allowed).
- **Currency picker** (`CurrencyPicker` on `FormState`, `form_input::handle_currency_picker_key` — early interceptor in `key_handler/mod.rs`): on **Currency**, **Enter** / **Space** / typing opens a searchable dropdown anchored under the row. Options come from `MostroInstanceInfo.fiat_currencies_accepted` when non-empty, else the bundled ISO-4217 list in `currencies.rs` (code + human name; no emoji flags — terminal fonts rarely render them). **↑/↓** move, **Enter** selects, **Esc** closes; filter matches code prefix or name substring.
- **Submit**: **Enter** on a complete form opens `ConfirmingOrder` (YES/NO); **Esc** cancels and clears `order_form_draft`.
- **Leave guard**: **Left** / **Right** tab navigation while `form.is_dirty()` opens `ConfirmLeaveOrder` (`order_confirm::render_leave_confirm`): default **Keep editing** (left), **Leave** (right) saves draft to `AppState.order_form_draft` and switches tab. Returning to Create New Order restores the draft (`navigation::restore_or_new_form`, auto-init in `draw.rs` when tab is active in `Normal` mode).
- **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**). On **Currency**, the picker interceptor runs first and consumes most keys while the dropdown is open. 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 @@ -278,13 +293,68 @@ Displays a list of direct messages related to the user's trades. Messages are tr

**Source**: `src/ui/tab_content.rs` (`render_messages_tab`, `render_message_view`, `render_rating_order`)

### 3. Order Form

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

**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)
### 3. Order Form (Create New Order)

Stateful form for publishing buy/sell orders. Supports fixed amounts, market price (`0` sats), fiat ranges, and optional Lightning invoice / expiration.

**Source**: `src/ui/order_form.rs`, `src/ui/order_confirm.rs`, `src/ui/currencies.rs`, `src/ui/key_handler/form_input.rs`, `src/ui/key_handler/navigation.rs`

#### State

| Type | Location | Role |
|------|----------|------|
| `FormState` | `src/ui/orders.rs` | Field values, `focused`, `use_range`, embedded `CurrencyPicker` |
| `FormField` | `src/ui/orders.rs` | Order Type, Currency, Amount (sats), Fiat (+ max when range), Payment Method, Premium, Invoice, Expiration |
| `CurrencyPicker` | `src/ui/orders.rs` | `open`, `filter`, `selected` (index into filtered list) |
| `order_form_draft` | `AppState` | `Option<FormState>` — draft kept when user confirms **Leave** on tab switch |
| `UserMode::CreatingOrder` | `src/ui/user_state.rs` | Active editing |
| `UserMode::ConfirmingOrder` | `src/ui/user_state.rs` | Pre-submit YES/NO popup |
| `UserMode::ConfirmLeaveOrder` | `src/ui/user_state.rs` | Unsaved-draft guard on **Left**/**Right** tab navigation |
| `UserMode::WaitingForMostro` | `src/ui/user_state.rs` | Async `send_new_order` in flight |

`FormState::is_dirty()` compares against `new_default_form()` (non-default kind/currency/amount/premium/expiry, range mode, or any non-empty fiat/payment/invoice text) to decide whether tab navigation should prompt.

#### Rendering (`render_order_form`)

- **Signature**: `render_order_form(f, area, form, info: Option<&MostroInstanceInfo>)` — instance info supplies `fiat_currencies_accepted`, `min_order_amount`, `max_order_amount` (limits hint under Amount when set).
- **Panels**:
- **Order details** — three sections with vertically centered content inside the panel:
- **TRADE**: Type (colored buy/sell dot), Currency (bold cyan code + dim name + `▾ pick`)
- **PRICING**: Amount (`market` when 0/empty), Fiat min (+ Fiat max when range)
- **TERMS**: Method, Premium, Invoice, Expiry
- **Live preview** — rounded **Order** receipt card with implied `≈ …/BTC` when computable; status dot (`ready` / `fill: …` / `invalid: …`).
- **Field help** — one-line contextual help for the focused field (`build_field_help`).
- **Footer** — `Enter` submit / `Tab` focus / `Space` toggle / `Esc` cancel.
- **Currency dropdown** (`render_currency_dropdown`): modal overlay when `currency_picker.open`; list shows bold ISO code + dim name; title includes accepted count from instance or “common” fallback list.

#### Currency metadata (`src/ui/currencies.rs`)

- `CURRENCIES`: curated ISO-4217 table (`code`, `name`).
- `resolve_options(accepted)`: instance-advertised codes when non-empty, else full bundled list; unknown instance codes get code-only rows.
- `filter_options(options, query)`: case-insensitive code-prefix or name-substring filter.
- `lookup` / `name_for`: enrich display strings (confirmation popup, selected currency row).
- **No emoji flags** in the UI — regional-indicator emoji degrade to letter pairs in most terminal fonts.

#### Tab lifecycle (`src/ui/draw.rs`, `navigation.rs`)

1. User opens **Create New Order** → `CreatingOrder(restore_or_new_form())` (draft or default).
2. Dirty form + **Left**/**Right** → `ConfirmLeaveOrder`; **Keep editing** returns to form; **Leave** stores `order_form_draft` and switches tab.
3. User returns to tab while `mode == Normal` → `draw.rs` auto-promotes to `CreatingOrder` (consumes draft or fresh default) so the tab is never stuck on an empty shell.
4. Successful submit (`ConfirmingOrder` YES) or explicit **Esc** cancel clears `order_form_draft`.

#### Key files for codegen

```text
src/ui/order_form.rs # render_order_form, validation, preview
src/ui/currencies.rs # ISO list + resolve/filter helpers
src/ui/orders.rs # FormState, FormField, CurrencyPicker, is_dirty
src/ui/order_confirm.rs # render_order_confirm, render_leave_confirm
src/ui/draw.rs # tab render + ConfirmLeaveOrder overlay
src/ui/key_handler/form_input.rs # char/backspace + handle_currency_picker_key
src/ui/key_handler/navigation.rs # tab leave guard, restore_or_new_form
src/ui/key_handler/enter_handlers.rs # ConfirmingOrder / ConfirmLeaveOrder Enter
src/ui/key_handler/esc_handlers.rs # Esc on form / leave guard
```
Comment on lines +345 to +357

@coderabbitai coderabbitai Bot Jul 1, 2026

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.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add a language to the fenced code block (MD040).

Static analysis flags this fence for missing a language specifier.

📝 Suggested fix
-```
+```text
 src/ui/order_form.rs          # render_order_form, validation, preview
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#### Key files for codegen
```
src/ui/order_form.rs # render_order_form, validation, preview
src/ui/currencies.rs # ISO list + resolve/filter helpers
src/ui/orders.rs # FormState, FormField, CurrencyPicker, is_dirty
src/ui/order_confirm.rs # render_order_confirm, render_leave_confirm
src/ui/draw.rs # tab render + ConfirmLeaveOrder overlay
src/ui/key_handler/form_input.rs # char/backspace + handle_currency_picker_key
src/ui/key_handler/navigation.rs # tab leave guard, restore_or_new_form
src/ui/key_handler/enter_handlers.rs # ConfirmingOrder / ConfirmLeaveOrder Enter
src/ui/key_handler/esc_handlers.rs # Esc on form / leave guard
```
#### Key files for codegen
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 347-347: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/TUI_INTERFACE.md` around lines 345 - 357, Add a language specifier to
the fenced code block in the “Key files for codegen” section to satisfy MD040;
update the markdown fence around the file list in docs/TUI_INTERFACE.md to
include a language tag such as text, keeping the contents and surrounding
documentation unchanged.

Source: Linters/SAST tools

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

fixed

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.

Oops, something went wrong! Please try again later. 🐰 💔


### My Trades interactive mode (`user_my_trades_interactive`)

Expand Down
6 changes: 5 additions & 1 deletion src/ui/app_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use crate::ui::chat::{
use crate::ui::helpers::OrderChatListItem;
use crate::ui::navigation::{Tab, UserRole};
use crate::ui::orders::{
BuyerInvoicePreference, InvoiceInputState, KeyInputState, MessageNotification,
BuyerInvoicePreference, FormState, InvoiceInputState, KeyInputState, MessageNotification,
MessageViewState, OperationResult, OrderChatStaticHeader, OrderMessage, RatingOrderState,
};
use crate::ui::user_state::UserMode;
Expand Down Expand Up @@ -257,6 +257,9 @@ pub struct AppState {
/// When set, closing an OperationResult popup (ESC/ENTER) will exit the app.
/// Used for fatal errors that require a clean restart.
pub fatal_exit_on_close: bool,
/// Preserved New Order form draft so leaving/returning to the tab keeps input.
/// Cleared on explicit cancel (Esc) or successful submit.
pub order_form_draft: Option<FormState>,
}

impl AppState {
Expand Down Expand Up @@ -320,6 +323,7 @@ impl AppState {
pending_fetch_scheduler_reload: false,
pending_post_take_operation_result: None,
fatal_exit_on_close: false,
order_form_draft: None,
}
}

Expand Down
Loading