Skip to content

feat(my-trades): receive attachments#80

Open
arkanoider wants to merge 1 commit into
mainfrom
feat/my-trades-chat-attachments-scroll
Open

feat(my-trades): receive attachments#80
arkanoider wants to merge 1 commit into
mainfrom
feat/my-trades-chat-attachments-scroll

Conversation

@arkanoider
Copy link
Copy Markdown
Collaborator

@arkanoider arkanoider commented May 31, 2026

Summary

  • Receive attachments on My Trades order chat (Phase A): parse Mostro Mobile image_encrypted / file_encrypted messages from relay sync, show yellow attachment lines + file count in chat title + receive toast, and save via Ctrl+S (UserSaveAttachmentPopup) with Blossom download and shared-key decrypt (order_chat_decryption_key_bytes).
  • Fix order chat scrolling: wire PgUp/PgDn/End, use ScrollView with full content height and always-visible scrollbar (same pattern as admin/observer), auto-scroll on new messages / order switch / send.
  • Fix duplicate self-messages: skip relay echoes where sender_pubkey == local_trade_pubkey and dedupe by (timestamp, content) so optimistic You sends are not mirrored as Peer (matches admin chat + Mostro Mobile behavior).
  • Docs: update MESSAGE_FLOW_AND_PROTOCOL.md, TUI_INTERFACE.md, STARTUP_AND_CONFIG.md, DATABASE.md, and README.md.

Summary by CodeRabbit

  • Documentation

    • Expanded documentation on user order chat ("My Trades") startup restore, relay synchronization, attachment decryption, and UI behaviors.
  • New Features

    • Added user order chat attachment saving with Ctrl+S shortcut on the My Trades tab.
    • Introduced chat scrolling controls (PageUp/PageDown to scroll, End to jump to latest).
    • Messages with attachments now display in yellow for visibility.
    • Added attachment toast notifications and save status feedback.

… echoes

Add Phase A user order chat parity with admin dispute chat: parse and display
encrypted attachments, Ctrl+S save popup with shared-key decrypt, ScrollView
scrolling (PgUp/PgDn/End), and skip relay echoes of the local trade key so
sent messages no longer appear on both You and Peer sides.

Co-authored-by: Cursor <cursoragent@cursor.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 31, 2026

Review Change Stack

Walkthrough

This PR implements attachment-saving functionality for the user role's "My Trades" order chat. It adds a new UI mode variant for an attachment-selection popup (Ctrl+S), refactors chat-update application to parse and display attachments with toast notifications, provides helpers for attachment visibility and chat scrolling, and updates the order-chat panel with attachment indicators and footer shortcuts.

Changes

Order chat attachment saving

Layer / File(s) Summary
UI types, state, and constants
src/ui/app_state.rs, src/ui/chat.rs, src/ui/constants.rs, src/ui/help_popup.rs
UiMode::UserSaveAttachmentPopup(usize) variant for the attachment-selection popup, OrderChatUpdate::local_trade_pubkey to filter relay echoes, and HELP_MY_TRADES_CTRL_S_ATTACH shortcut constant.
Chat update application with attachment parsing
src/ui/helpers/startup.rs, src/util/chat_utils.rs
apply_user_order_chat_updates parses attachments, skips relay echoes by local_trade_pubkey, dedupes by (timestamp, parsed_content), and sets attachment toast; order_chat_decryption_key_bytes derives or restores the ECDH shared secret for decryption.
Attachment visibility and scroll helpers
src/ui/helpers/chat_visibility.rs, src/ui/helpers/mod.rs, src/ui/key_handler/chat_helpers.rs
get_order_attachment_messages and count_order_attachments retrieve attachments by order_id; scroll_order_chat_messages, jump_to_order_chat_bottom, and scroll_order_chat_after_send manage chat navigation.
Key event handling and UI mode dispatch
src/ui/key_handler/mod.rs, src/ui/key_handler/enter_handlers.rs, src/ui/key_handler/esc_handlers.rs, src/ui/key_handler/navigation.rs
Ctrl+S opens UserSaveAttachmentPopup when attachments exist; Up/Down navigate attachments with async key-derivation on Enter; Esc cancels; PageUp/PgDn scroll chat; End jumps to bottom; popup mode prevents normal navigation.
UI rendering: popup, chat, and footer
src/ui/draw.rs, src/ui/save_attachment_popup.rs, src/ui/tabs/order_in_progress_tab.rs
render_user_save_attachment_popup displays the selected attachment; Order Chat panel shows file count in title, colors attachment messages yellow, auto-scrolls on growth, adds Ctrl+S hint, and renders attachment toast above footer shortcuts.
Feature documentation
docs/DATABASE.md, docs/MESSAGE_FLOW_AND_PROTOCOL.md, docs/README.md, docs/STARTUP_AND_CONFIG.md, docs/TUI_INTERFACE.md
Documents My Trades attachment saving, order chat restore/relay sync at startup, dedup/echo-skip rules, chat scrolling, attachment UI indicators, popup flow, and key bindings.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • MostroP2P/mostrix#52: Main PR extended by adding attachment visibility helpers and updated user order chat startup/update application path with attachment parsing and decryption key derivation.
  • MostroP2P/mostrix#44: UI mode and key-handler dispatch machinery overlap; this PR introduces UserSaveAttachmentPopup variant and related enter/esc handling alongside existing admin and observer save-attachment modes.

Suggested reviewers

  • mostronatorcoder
  • grunch
  • ermeme

Poem

🐰 Attachments now save with a twist and a hop,
My Trades chat displays them, the colors won't stop!
With Ctrl+S magic and scrolls up and down,
The popup's now ready—best feature in town! 🎉

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(my-trades): receive attachments' clearly and concisely summarizes the main feature addition—enabling users to receive encrypted attachments in My Trades order chat with associated UI/UX enhancements.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/my-trades-chat-attachments-scroll

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.

Copy link
Copy Markdown
Contributor

@mostronatorcoder mostronatorcoder Bot left a comment

Choose a reason for hiding this comment

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

Reviewed strictly. No blocker.

What I checked:

Attachment receive + dedup
The relay-echo skip (sender_pubkey == update.local_trade_pubkey) matches the pattern used in admin chat. The dedup by (timestamp, content) is applied after the echo skip, so it only catches genuine duplicates from concurrent relay syncs, not our own messages. That is correct.

Decryption key resolution
order_chat_decryption_key_bytes tries order_chat_shared_key_hex first (negotiated key), then falls back to ECDH from trade_keys + counterparty_pubkey. That is the right priority. The decryption key is looked up at save time (lazy), not at parse time, which is fine.

UserSaveAttachmentPopup dispatch
I checked the dispatch order carefully. All keys including Enter are consumed and return Some(true) inside the UserSaveAttachmentPopup block in mod.rs, so the dead-code arm in enter_handlers.rs is never reached. Not a bug, just cosmetically redundant (same pattern as ObserverSaveAttachmentPopup).

Scroll tracking
The order_chat_scroll_tracker is keyed by order ID string and updated on every render pass. Auto-scroll fires on new messages and on order switch. The scroll_to_bottom() on send is also wired correctly via scroll_order_chat_after_send. I do not see a way for the scroll state to get out of sync.

Toast lifetime
The toast is set on attachment receive and rendered in the footer. It is not cleared on a timer, but this matches the existing attachment_toast pattern used elsewhere in the codebase — not a regression.

One minor nit (non-blocking): the arm in enter_handlers.rs for UserSaveAttachmentPopup is dead code (never reached because mod.rs already returns before calling handle_enter_key). Worth a comment like the ObserverSaveAttachmentPopup arm above it, but it does not affect correctness.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

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

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

176-197: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Keep orders schema snippet consistent with the field table.

order_chat_shared_key_hex is documented in the field list (Line 218) but missing from the SQL schema block above, so readers get conflicting schema definitions.

Suggested doc patch
 CREATE TABLE IF NOT EXISTS orders (
     id TEXT PRIMARY KEY,
     kind TEXT,
     status TEXT,
     amount INTEGER NOT NULL,
     fiat_code TEXT NOT NULL,
     min_amount INTEGER,
     max_amount INTEGER,
     fiat_amount INTEGER NOT NULL,
     payment_method TEXT NOT NULL,
     premium INTEGER NOT NULL,
     trade_keys TEXT,
     counterparty_pubkey TEXT,
+    order_chat_shared_key_hex TEXT,
     is_mine INTEGER NOT NULL,
     buyer_invoice TEXT,
     request_id INTEGER,
     trade_index INTEGER,
     created_at INTEGER,
     expires_at INTEGER,
     last_seen_dm_ts INTEGER
 );

Also applies to: 218-218

🤖 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/DATABASE.md` around lines 176 - 197, The CREATE TABLE orders SQL block
is missing the documented field order_chat_shared_key_hex; update the orders
table DDL in the SQL snippet to include the order_chat_shared_key_hex column and
ensure its datatype and nullability match the field list (i.e., add a column
named order_chat_shared_key_hex with the same type—likely TEXT—and the same NOT
NULL or nullable constraint as documented) so the SQL schema and the field table
stay consistent.
src/ui/tabs/order_in_progress_tab.rs (1)

352-371: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Account for the toast row when sizing the footer.

can_fit_two_line_footer / can_fit_three_line_footer are computed before the toast row is added. With only 3-4 spare rows, attachment_toast can consume the last chat row and leave the chat pane at zero height until the toast expires.

🤖 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 `@src/ui/tabs/order_in_progress_tab.rs` around lines 352 - 371, The footer
sizing currently computes can_fit_three_line_footer / can_fit_two_line_footer
from spare_below_header_input before accounting for app.attachment_toast, which
can steal the last chat row; update the logic so the toast row is reserved when
deciding footer size by computing a spare_after_toast (e.g.
spare_below_header_input.saturating_sub(if app.attachment_toast.is_some() { 1 }
else { 0 })) and use spare_after_toast when setting can_fit_three_line_footer
and can_fit_two_line_footer and subsequently determining footer_height (symbols:
spare_below_header_input, can_fit_three_line_footer, can_fit_two_line_footer,
footer_height, app.attachment_toast).
🧹 Nitpick comments (1)
src/util/chat_utils.rs (1)

74-88: ⚡ Quick win

Add a regression test for both decryption-key branches.

order_chat_decryption_key_bytes now chooses between persisted shared-key hex and on-the-fly ECDH derivation. A small unit test that asserts both paths produce the same 32-byte key for the same order would harden the attachment-save path against silent crypto regressions.

🤖 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 `@src/util/chat_utils.rs` around lines 74 - 88, Add a unit test that constructs
an Order and validates both branches of order_chat_decryption_key_bytes produce
the same 32-byte key: (1) prepare an order with order_chat_shared_key_hex
populated (use keys_from_shared_hex to generate or a known valid shared-hex) and
call order_chat_decryption_key_bytes to get the persisted-hex path result; (2)
prepare an equivalent order without order_chat_shared_key_hex but with
trade_keys (SecretKey -> Keys) and counterparty_pubkey (PublicKey) that
correspond to the same ECDH secret and call order_chat_decryption_key_bytes to
exercise the derive_shared_keys path; assert both outputs are Some with length
32 and equal. Use the existing helpers keys_from_shared_hex, derive_shared_keys,
Keys, SecretKey::from_str and PublicKey::parse to build matching key material so
the test deterministically compares the two branches.
🤖 Prompt for all review comments with 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.

Inline comments:
In `@docs/MESSAGE_FLOW_AND_PROTOCOL.md`:
- Line 309: The doc contradicts itself about the poll interval: update the
wording so the user polling interval and the admin interval are consistent —
either rename the user timer to a distinct symbol (e.g., user_chat_interval used
by spawn_user_order_chat_fetch and referenced where
fetch_user_order_chat_updates is described) or state that
spawn_user_order_chat_fetch uses the same admin_chat_interval
(admin_chat_interval) and set the value consistently (e.g., 5 seconds) in both
places; ensure you update both occurrences of the interval statement and any
mention of admin_chat_interval, spawn_user_order_chat_fetch,
fetch_user_order_chat_updates, CHAT_MESSAGES_SEMAPHORE, and
order_chat_shared_key_hex/trade_keys+counterparty_pubkey to match the chosen
name and value.

In `@src/ui/helpers/startup.rs`:
- Around line 331-338: The dedupe check in the loop (using
messages_vec.iter().any(...) and the is_duplicate branch) is incorrectly
collapsing optimistic local “You” messages; change the predicate so it only
treats a message as duplicate if the existing message is from a remote peer
(e.g., check that the existing message's sender_pubkey != local_pubkey or that
it is not a local/You message) before comparing timestamp and content, and only
then update max_ts and continue; keep the existing relay/sender_pubkey filtering
intact by referencing the same sender_pubkey/local identity check used earlier
in this function.

In `@src/ui/key_handler/mod.rs`:
- Around line 592-600: The popup currently re-resolves the target order from
active_order_chat_list_snapshot using app.selected_order_chat_idx which can
change; modify the UiMode::UserSaveAttachmentPopup variant to carry the frozen
order_id (e.g., UiMode::UserSaveAttachmentPopup(order_id: OrderId)) and update
the code paths that open the popup to store the originating order id; then
change all places that call
active_order_chat_list_snapshot(...).get(app.selected_order_chat_idx) and any
subsequent get_order_attachment_messages(app, id) (including the blocks around
the current UserSaveAttachmentPopup handling and the other two spots mentioned)
to use the stored order_id from the UiMode instead of re-reading the live
snapshot so rendering and save operations always use the pinned order id.

---

Outside diff comments:
In `@docs/DATABASE.md`:
- Around line 176-197: The CREATE TABLE orders SQL block is missing the
documented field order_chat_shared_key_hex; update the orders table DDL in the
SQL snippet to include the order_chat_shared_key_hex column and ensure its
datatype and nullability match the field list (i.e., add a column named
order_chat_shared_key_hex with the same type—likely TEXT—and the same NOT NULL
or nullable constraint as documented) so the SQL schema and the field table stay
consistent.

In `@src/ui/tabs/order_in_progress_tab.rs`:
- Around line 352-371: The footer sizing currently computes
can_fit_three_line_footer / can_fit_two_line_footer from
spare_below_header_input before accounting for app.attachment_toast, which can
steal the last chat row; update the logic so the toast row is reserved when
deciding footer size by computing a spare_after_toast (e.g.
spare_below_header_input.saturating_sub(if app.attachment_toast.is_some() { 1 }
else { 0 })) and use spare_after_toast when setting can_fit_three_line_footer
and can_fit_two_line_footer and subsequently determining footer_height (symbols:
spare_below_header_input, can_fit_three_line_footer, can_fit_two_line_footer,
footer_height, app.attachment_toast).

---

Nitpick comments:
In `@src/util/chat_utils.rs`:
- Around line 74-88: Add a unit test that constructs an Order and validates both
branches of order_chat_decryption_key_bytes produce the same 32-byte key: (1)
prepare an order with order_chat_shared_key_hex populated (use
keys_from_shared_hex to generate or a known valid shared-hex) and call
order_chat_decryption_key_bytes to get the persisted-hex path result; (2)
prepare an equivalent order without order_chat_shared_key_hex but with
trade_keys (SecretKey -> Keys) and counterparty_pubkey (PublicKey) that
correspond to the same ECDH secret and call order_chat_decryption_key_bytes to
exercise the derive_shared_keys path; assert both outputs are Some with length
32 and equal. Use the existing helpers keys_from_shared_hex, derive_shared_keys,
Keys, SecretKey::from_str and PublicKey::parse to build matching key material so
the test deterministically compares the two branches.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 509f7263-5b5f-4e70-8ff0-3ee9153c284d

📥 Commits

Reviewing files that changed from the base of the PR and between c9c88a3 and a722b09.

📒 Files selected for processing (21)
  • docs/DATABASE.md
  • docs/MESSAGE_FLOW_AND_PROTOCOL.md
  • docs/README.md
  • docs/STARTUP_AND_CONFIG.md
  • docs/TUI_INTERFACE.md
  • src/ui/app_state.rs
  • src/ui/chat.rs
  • src/ui/constants.rs
  • src/ui/draw.rs
  • src/ui/help_popup.rs
  • src/ui/helpers/chat_visibility.rs
  • src/ui/helpers/mod.rs
  • src/ui/helpers/startup.rs
  • src/ui/key_handler/chat_helpers.rs
  • src/ui/key_handler/enter_handlers.rs
  • src/ui/key_handler/esc_handlers.rs
  • src/ui/key_handler/mod.rs
  • src/ui/key_handler/navigation.rs
  • src/ui/save_attachment_popup.rs
  • src/ui/tabs/order_in_progress_tab.rs
  • src/util/chat_utils.rs

- **Startup restore**: `load_user_order_chats_at_startup` restores cached chat into `AppState.order_chats` and seeds `order_chat_last_seen` before relay backfill.
- **Incremental merge**: `apply_user_order_chat_updates` deduplicates by `(timestamp, content)`, persists new entries, and advances per-order cursors.
- **Startup restore**: `load_user_order_chats_at_startup` restores cached chat into `AppState.order_chats`, seeds `order_chat_last_seen`, then runs an initial `fetch_user_order_chat_updates` relay backfill.
- **Periodic relay sync (User role)**: every 2 seconds (`admin_chat_interval` in `src/main.rs`), `spawn_user_order_chat_fetch` polls active orders via `fetch_user_order_chat_updates`. Uses the same single-flight `CHAT_MESSAGES_SEMAPHORE` as admin chat so overlapping fetches are skipped. Shared keys come from persisted `order_chat_shared_key_hex` when set, otherwise ECDH from local `trade_keys` + `counterparty_pubkey` (`src/util/chat_utils.rs`).
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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Clarify interval naming/value to avoid contradiction in this doc.

Line 309 says user polling is every 2 seconds via admin_chat_interval, but later this same doc describes admin_chat_interval as 5 seconds for admin chat. Please either use a distinct timer name for user chat or align the interval statement so both sections agree.

🤖 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/MESSAGE_FLOW_AND_PROTOCOL.md` at line 309, The doc contradicts itself
about the poll interval: update the wording so the user polling interval and the
admin interval are consistent — either rename the user timer to a distinct
symbol (e.g., user_chat_interval used by spawn_user_order_chat_fetch and
referenced where fetch_user_order_chat_updates is described) or state that
spawn_user_order_chat_fetch uses the same admin_chat_interval
(admin_chat_interval) and set the value consistently (e.g., 5 seconds) in both
places; ensure you update both occurrences of the interval statement and any
mention of admin_chat_interval, spawn_user_order_chat_fetch,
fetch_user_order_chat_updates, CHAT_MESSAGES_SEMAPHORE, and
order_chat_shared_key_hex/trade_keys+counterparty_pubkey to match the chosen
name and value.

Comment thread src/ui/helpers/startup.rs
Comment on lines +331 to 338
let is_duplicate = messages_vec
.iter()
.any(|m| m.timestamp == msg.timestamp && m.content == msg.content);
if duplicated {
.any(|m| m.timestamp == ts && m.content == msg_content);
if is_duplicate {
if ts > max_ts {
max_ts = ts;
}
continue;
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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Limit deduplication to existing peer messages.

Relay echoes are already filtered above on sender_pubkey, so this second pass should not collapse against locally inserted You messages. As written, a peer message with the same second-level timestamp and content as an optimistic local send gets dropped.

💡 Suggested fix
             let is_duplicate = messages_vec
                 .iter()
-                .any(|m| m.timestamp == ts && m.content == msg_content);
+                .any(|m| {
+                    m.sender == UserChatSender::Peer
+                        && m.timestamp == ts
+                        && m.content == msg_content
+                });
📝 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
let is_duplicate = messages_vec
.iter()
.any(|m| m.timestamp == msg.timestamp && m.content == msg.content);
if duplicated {
.any(|m| m.timestamp == ts && m.content == msg_content);
if is_duplicate {
if ts > max_ts {
max_ts = ts;
}
continue;
let is_duplicate = messages_vec
.iter()
.any(|m| {
m.sender == UserChatSender::Peer
&& m.timestamp == ts
&& m.content == msg_content
});
if is_duplicate {
if ts > max_ts {
max_ts = ts;
}
continue;
🤖 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 `@src/ui/helpers/startup.rs` around lines 331 - 338, The dedupe check in the
loop (using messages_vec.iter().any(...) and the is_duplicate branch) is
incorrectly collapsing optimistic local “You” messages; change the predicate so
it only treats a message as duplicate if the existing message is from a remote
peer (e.g., check that the existing message's sender_pubkey != local_pubkey or
that it is not a local/You message) before comparing timestamp and content, and
only then update max_ts and continue; keep the existing relay/sender_pubkey
filtering intact by referencing the same sender_pubkey/local identity check used
earlier in this function.

Comment thread src/ui/key_handler/mod.rs
Comment on lines +592 to +600
// User order chat save attachment popup: Up/Down to select, Enter to save, Esc to cancel
if matches!(app.mode, UiMode::UserSaveAttachmentPopup(_)) {
let order_id = active_order_chat_list_snapshot(app)
.get(app.selected_order_chat_idx)
.map(|row| row.order_id.clone());
let list_len = order_id
.as_ref()
.map(|id| get_order_attachment_messages(app, id).len())
.unwrap_or(0);
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.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Pin the popup to an order_id, not the current sidebar index.

This mode recomputes the active order from a fresh active_order_chat_list_snapshot() both when opening and when saving. If a background update reorders My Trades while the popup is open, the same selected_order_chat_idx can resolve to a different order and save the wrong attachment.

Store the originating order_id in UiMode::UserSaveAttachmentPopup and use that frozen id for render/save lookup instead of re-reading the live snapshot.

Also applies to: 626-647, 779-787

🤖 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 `@src/ui/key_handler/mod.rs` around lines 592 - 600, The popup currently
re-resolves the target order from active_order_chat_list_snapshot using
app.selected_order_chat_idx which can change; modify the
UiMode::UserSaveAttachmentPopup variant to carry the frozen order_id (e.g.,
UiMode::UserSaveAttachmentPopup(order_id: OrderId)) and update the code paths
that open the popup to store the originating order id; then change all places
that call active_order_chat_list_snapshot(...).get(app.selected_order_chat_idx)
and any subsequent get_order_attachment_messages(app, id) (including the blocks
around the current UserSaveAttachmentPopup handling and the other two spots
mentioned) to use the stored order_id from the UiMode instead of re-reading the
live snapshot so rendering and save operations always use the pinned order id.

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.

1 participant