Improve My Trades chat and refactor UI helpers#52
Conversation
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughAdds a per-order P2P "Order Chat" feature with on-disk per-order transcript caching, DB persistence for per-order shared chat keys, UI rendering/input for order chats, background fetch/send flows, and refactors the monolithic Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant App as App (Startup)
participant DB as SQLite
participant Disk as Disk Cache
participant Nostr as Nostr Client
User->>App: Launch (User role)
App->>DB: Load active orders
App->>Disk: load_order_chat_from_file(order_id)
Disk-->>App: Cached messages
App->>App: Initialize app.order_chats & order_chat_last_seen
App->>Nostr: fetch_user_order_chat_updates(shared_key, last_seen)
Nostr-->>App: New giftwrapped messages
App->>App: apply_user_order_chat_updates() (dedupe by timestamp+content)
App->>Disk: save_order_chat_message(order_id, msg)
App->>DB: Persist last-seen timestamps
sequenceDiagram
participant User
participant UI as UI (Order Chat Tab)
participant App as AppState
participant Disk as Disk Cache
participant Nostr as Nostr Client
User->>UI: Selects order
UI->>App: Get app.order_chats[order_id]
App-->>UI: Cached messages
UI->>UI: render order chat
User->>UI: Types message + Enter
UI->>App: Append local UserOrderChatMessage
UI->>Disk: save_order_chat_message(order_id, msg)
UI->>Nostr: send_user_order_chat_message_via_shared_key() (background)
Nostr-->>UI: Send result (async)
UI->>App: Clear input
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (8)
src/ui/helpers/formatting.rs (1)
21-23:is_dispute_finalizedcan returnbooldirectly.This helper always returns
Some(...); returningboolsimplifies call sites and avoids unnecessary wrapping.♻️ Proposed refactor
-pub fn is_dispute_finalized(selected_dispute: &AdminDispute) -> Option<bool> { - Some(selected_dispute.is_finalized()) +pub fn is_dispute_finalized(selected_dispute: &AdminDispute) -> bool { + selected_dispute.is_finalized() }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/ui/helpers/formatting.rs` around lines 21 - 23, Change is_dispute_finalized to return a plain bool instead of Option<bool>: update the function signature from pub fn is_dispute_finalized(selected_dispute: &AdminDispute) -> Option<bool> to -> bool and simply return selected_dispute.is_finalized(). Also search for call sites that expect Option<bool> and remove the Some(...) handling (e.g., replace comparisons with unwraps or direct bool usage) to match the new bool return type.src/ui/chat.rs (1)
106-109: Consider replacing raw message tuples with a named payload type.
Vec<(String, i64, PublicKey)>is compact but brittle. A named struct improves readability and reduces ordering mistakes.♻️ Suggested refactor
+#[derive(Clone, Debug)] +pub struct PolledChatMessage { + pub content: String, + pub timestamp: i64, + pub sender_pubkey: PublicKey, +} + pub struct OrderChatUpdate { pub order_id: String, - /// (content, timestamp, sender_pubkey) - pub messages: Vec<(String, i64, PublicKey)>, + pub messages: Vec<PolledChatMessage>, }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/ui/chat.rs` around lines 106 - 109, Replace the brittle tuple type used in OrderChatUpdate.messages with a named struct to improve readability and safety: define a ChatMessage (or similar) struct containing fields content: String, timestamp: i64, and sender_pubkey: PublicKey, then change OrderChatUpdate.messages from Vec<(String, i64, PublicKey)> to Vec<ChatMessage> and update all constructors, pattern matches, serializations, and usages (e.g., creation sites, destructuring, and any serde derives) to use the new struct fields instead of tuple indexes.src/main.rs (1)
248-248: Consider guarding startup hydration by user role.
load_user_order_chats_at_startupis called unconditionally, but user order chats are only relevant forUserRole::User. While this is not a bug (admin mode will simply have no active user orders), you could skip the DB/relay work when starting in admin mode for a minor efficiency gain.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main.rs` at line 248, Only call load_user_order_chats_at_startup when the running role is a regular user: wrap the call in a conditional that checks the app's active role (match or if let with UserRole::User) and skip the DB/relay hydration for admin mode; reference the UserRole enum and the function load_user_order_chats_at_startup(&client, &pool, &mut app).await and ensure you import or reference UserRole where needed so admin startup path avoids the unnecessary work.src/ui/tabs/order_in_progress_tab.rs (1)
117-149: Consider extracting duplicatewrap_text_to_lineshelper.This function duplicates the implementation in
src/ui/helpers/chat_render.rs. Consider importing and reusing the existing helper to maintain DRY principles.Suggested approach
+use crate::ui::helpers::chat_render::wrap_text_to_lines; + fn build_order_chat_content( messages: &[UserOrderChatMessage], content_width: u16, ) -> (Vec<Line<'static>>, u16, Vec<usize>) { - fn wrap_text_to_lines(content: &str, max_width: u16) -> Vec<String> { - // ... 30+ lines of duplicate code - } // rest of function using imported wrap_text_to_linesNote: You may need to adjust visibility in
chat_render.rsifwrap_text_to_linesis not alreadypub.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/ui/tabs/order_in_progress_tab.rs` around lines 117 - 149, Duplicate wrap_text_to_lines implementation should be removed and the existing helper reused: change this file to call the central wrap_text_to_lines function from the chat_render module (e.g. use crate::ui::helpers::chat_render::wrap_text_to_lines) instead of redefining it; if the helper in chat_render.rs is not public, make it public (pub or pub(crate) as appropriate) and update the use/import in order_in_progress_tab.rs so the single shared function is used. Ensure tests/build still pass after adjusting visibility and remove the local wrap_text_to_lines function definition.src/ui/helpers/startup.rs (3)
266-276: Automatic focus jump on attachment detection may be surprising.When an attachment is received, the code automatically switches
selected_in_progress_idxandactive_chat_party, pulling the user's focus to that dispute/party. If this is intentional behavior to draw attention to attachments, consider documenting it. If not, you may want to only show the toast without changing selection.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/ui/helpers/startup.rs` around lines 266 - 276, The code currently sets app.attachment_toast and then unconditionally updates app.selected_in_progress_idx and app.active_chat_party when attachment_opt is Some, which forces the UI focus jump; to prevent this, change the block handling attachment_opt so it only calls app.attachment_toast = Some(build_attachment_toast(filename_for_toast)) and does not modify app.selected_in_progress_idx or app.active_chat_party (or guard those assignments behind an explicit flag/setting if intentional); update the handling around attachment_opt, build_attachment_toast, and the admin_disputes_in_progress position lookup (the .position(|d| d.dispute_id == dispute_key)) to be removed or conditional so receiving an attachment shows the toast only without changing selection.
59-64: Unnecessary clone of messages vector.
load_chat_from_filereturns an ownedVec<DisputeChatMessage>. The.clone()on line 60 creates an extra copy when computing timestamps, but the originalmsgscould be moved into the map and a reference used formax_party_timestamps.♻️ Proposed fix
if let Some(msgs) = load_chat_from_file(&dispute.dispute_id) { - admin_dispute_chats.insert(dispute.dispute_id.clone(), msgs.clone()); - let (buyer_max, seller_max) = max_party_timestamps(&msgs); + let (buyer_max, seller_max) = max_party_timestamps(&msgs); + admin_dispute_chats.insert(dispute.dispute_id.clone(), msgs); update_last_seen_timestamp(buyer_max, seller_max, dispute, admin_chat_last_seen); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/ui/helpers/startup.rs` around lines 59 - 64, Move the owned Vec returned by load_chat_from_file into the admin_dispute_chats map instead of cloning it: call let msgs = load_chat_from_file(&dispute.dispute_id).unwrap_or_default(); then insert with admin_dispute_chats.insert(dispute.dispute_id.clone(), msgs); obtain a reference to the stored messages (e.g. let stored = admin_dispute_chats.get(&dispute.dispute_id).unwrap()) and pass that reference to max_party_timestamps(&stored) so you avoid the unnecessary msgs.clone() while keeping update_last_seen_timestamp(buyer_max, seller_max, dispute, admin_chat_last_seen) unchanged.
125-146: Consider logging errors instead of silently discarding them.Lines 125-127 and 143-145 silently discard errors. While it's reasonable not to block startup on failures, logging would help diagnose issues:
♻️ Proposed fix
- let Ok(rows) = Order::get_startup_active_orders(pool).await else { + let rows = match Order::get_startup_active_orders(pool).await { + Ok(r) => r, + Err(e) => { + log::warn!("Failed to load active orders at startup: {}", e); + return; + } + }; ... - let updates = fetch_user_order_chat_updates(client, pool, &app.order_chat_last_seen) - .await - .unwrap_or_default(); + let updates = match fetch_user_order_chat_updates(client, pool, &app.order_chat_last_seen).await { + Ok(u) => u, + Err(e) => { + log::warn!("Failed to fetch user order chat updates: {}", e); + return; + } + };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/ui/helpers/startup.rs` around lines 125 - 146, The code currently swallows errors from Order::get_startup_active_orders(...) and fetch_user_order_chat_updates(...). Replace the terse patterns with proper error handling: when calling Order::get_startup_active_orders(pool).await, match the Result and log the error (e.g., tracing::error! or log::error!) with contextual info instead of just returning; when calling fetch_user_order_chat_updates(client, pool, &app.order_chat_last_seen).await, handle the Result explicitly (match/if let Err(e)) and log the error before falling back to default updates so startup continues; keep existing behavior of loading chats via load_order_chat_from_file(&order_id) but ensure any IO errors there are surfaced/logged by that function if applicable. Use the function names Order::get_startup_active_orders and fetch_user_order_chat_updates in your changes so the edits are easy to find.src/ui/helpers/chat_render.rs (1)
178-206: Consider extracting shared logic withbuild_chat_scrollview_content.This function is nearly identical to
build_chat_scrollview_content, differing only in the absence of party filtering and the placeholder message. A helper could reduce duplication:fn build_scrollview_content_impl( messages: impl Iterator<Item = &DisputeChatMessage>, content_width: u16, max_content_width: Option<u16>, empty_placeholder: &'static str, ) -> ChatScrollViewContent { ... }That said, the current duplication is minimal and the separate functions are clear about their purpose—this is a nice-to-have refinement.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/ui/helpers/chat_render.rs` around lines 178 - 206, Both build_observer_scrollview_content and build_chat_scrollview_content duplicate the same logic; extract the shared flow into a helper like build_scrollview_content_impl that accepts an iterator over &DisputeChatMessage, content_width, max_content_width, and an empty_placeholder &str; have the helper populate lines by calling format_message_lines, record line_start_per_message, push the styled placeholder using Line::from(Span::styled(...)) when empty, compute content_height and return ChatScrollViewContent, and then replace the bodies of build_observer_scrollview_content and build_chat_scrollview_content to call this helper with the appropriate placeholder and message iterator.
🤖 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/DATABASE.md`:
- Around line 429-430: Add the missing orders.order_chat_shared_key_hex column
to the "Order Table Fields" section of DATABASE.md: add a row for
order_chat_shared_key_hex with type TEXT and the description "Hex-encoded shared
key used for user order chat cache/restore flow" so the docs match the current
DB schema and the new field used by order chat cache/restore logic.
In `@src/ui/helpers/formatting.rs`:
- Around line 9-14: The UI prints raw info.rating and only caps stars, so
out-of-range ratings still show (e.g., "6.2/5"); update the logic in the block
that defines star_count, stars, and the format! call to clamp the rating first
(use a displayed_rating = info.rating.clamp(0.0, 5.0) or equivalent), compute
star_count from displayed_rating (e.g., displayed_rating.round()) and use
displayed_rating in the format! output instead of info.rating so both stars and
the "X/5" text are bounded to 0–5.
In `@src/ui/helpers/startup.rs`:
- Around line 310-318: The code currently uses the `?` operator on
`AdminDispute::update_chat_last_seen_by_dispute_id(pool, &dispute_key, max_ts,
party == ChatParty::Buyer).await?`, which causes an early return on DB error and
can leave in-memory state (`admin_dispute_chats`, `admin_chat_last_seen`)
inconsistent; change this call to handle the Result explicitly (e.g., `match` or
`if let Err(e) = ...`) so errors are logged and processing continues for
subsequent disputes instead of returning, and ensure the log message includes
the `dispute_key`, `max_ts`, and `party` context so failures are observable
while in-memory updates remain intact.
In `@src/ui/key_handler/enter_handlers.rs`:
- Around line 550-565: The code currently re-derives selected_order_id by
iterating over app.messages (which can include hidden/completed trades) and so
can pick the wrong order; replace that logic to use the exact filtered/sorted
active-order projection used to render the MyTrades sidebar (the same projection
in order_in_progress_tab.rs) and index into that projection with
app.selected_order_chat_idx to obtain the order_id; ensure you access the
projection in a thread-safe way (locking or calling the same accessor used by
the UI), handle missing/None safely, and return early if the indexed projection
entry has no order_id.
In `@src/ui/key_handler/navigation.rs`:
- Around line 239-249: The navigation logic for Tab::User(UserTab::MyTrades)
currently bounds selected_order_chat_idx using raw app.messages (and a HashSet
of order_ids), causing index drift versus the rendered list; update both this
branch and the same logic at the other site (around the 389-396 region) to
compute the visible/deduped, status-filtered active-order list using the same
projection/function used by order_in_progress_tab.rs (the active-order rendering
helper) and then clamp/adjust app.selected_order_chat_idx against that rendered
list's length instead of app.messages; reference Tab::User(UserTab::MyTrades),
app.messages, app.selected_order_chat_idx, and the active-order projection in
order_in_progress_tab.rs to locate and replace the bounds calculation.
In `@src/util/chat_utils.rs`:
- Around line 418-423: The current filtering uses messages.retain(|(_, ts, _)|
*ts >= last_seen) which will include messages whose timestamp equals
order_chat_last_seen.last_seen_timestamp and can cause re-processing duplicates;
change the predicate to strictly greater (use > last_seen) when filtering the
vector returned by fetch_gift_wraps_for_shared_key so messages.retain(...) only
keeps entries with timestamp > last_seen (or alternatively de-duplicate
downstream), updating the call site where messages is filtered to reference
order_chat_last_seen and last_seen_timestamp accordingly.
---
Nitpick comments:
In `@src/main.rs`:
- Line 248: Only call load_user_order_chats_at_startup when the running role is
a regular user: wrap the call in a conditional that checks the app's active role
(match or if let with UserRole::User) and skip the DB/relay hydration for admin
mode; reference the UserRole enum and the function
load_user_order_chats_at_startup(&client, &pool, &mut app).await and ensure you
import or reference UserRole where needed so admin startup path avoids the
unnecessary work.
In `@src/ui/chat.rs`:
- Around line 106-109: Replace the brittle tuple type used in
OrderChatUpdate.messages with a named struct to improve readability and safety:
define a ChatMessage (or similar) struct containing fields content: String,
timestamp: i64, and sender_pubkey: PublicKey, then change
OrderChatUpdate.messages from Vec<(String, i64, PublicKey)> to Vec<ChatMessage>
and update all constructors, pattern matches, serializations, and usages (e.g.,
creation sites, destructuring, and any serde derives) to use the new struct
fields instead of tuple indexes.
In `@src/ui/helpers/chat_render.rs`:
- Around line 178-206: Both build_observer_scrollview_content and
build_chat_scrollview_content duplicate the same logic; extract the shared flow
into a helper like build_scrollview_content_impl that accepts an iterator over
&DisputeChatMessage, content_width, max_content_width, and an empty_placeholder
&str; have the helper populate lines by calling format_message_lines, record
line_start_per_message, push the styled placeholder using
Line::from(Span::styled(...)) when empty, compute content_height and return
ChatScrollViewContent, and then replace the bodies of
build_observer_scrollview_content and build_chat_scrollview_content to call this
helper with the appropriate placeholder and message iterator.
In `@src/ui/helpers/formatting.rs`:
- Around line 21-23: Change is_dispute_finalized to return a plain bool instead
of Option<bool>: update the function signature from pub fn
is_dispute_finalized(selected_dispute: &AdminDispute) -> Option<bool> to -> bool
and simply return selected_dispute.is_finalized(). Also search for call sites
that expect Option<bool> and remove the Some(...) handling (e.g., replace
comparisons with unwraps or direct bool usage) to match the new bool return
type.
In `@src/ui/helpers/startup.rs`:
- Around line 266-276: The code currently sets app.attachment_toast and then
unconditionally updates app.selected_in_progress_idx and app.active_chat_party
when attachment_opt is Some, which forces the UI focus jump; to prevent this,
change the block handling attachment_opt so it only calls app.attachment_toast =
Some(build_attachment_toast(filename_for_toast)) and does not modify
app.selected_in_progress_idx or app.active_chat_party (or guard those
assignments behind an explicit flag/setting if intentional); update the handling
around attachment_opt, build_attachment_toast, and the
admin_disputes_in_progress position lookup (the .position(|d| d.dispute_id ==
dispute_key)) to be removed or conditional so receiving an attachment shows the
toast only without changing selection.
- Around line 59-64: Move the owned Vec returned by load_chat_from_file into the
admin_dispute_chats map instead of cloning it: call let msgs =
load_chat_from_file(&dispute.dispute_id).unwrap_or_default(); then insert with
admin_dispute_chats.insert(dispute.dispute_id.clone(), msgs); obtain a reference
to the stored messages (e.g. let stored =
admin_dispute_chats.get(&dispute.dispute_id).unwrap()) and pass that reference
to max_party_timestamps(&stored) so you avoid the unnecessary msgs.clone() while
keeping update_last_seen_timestamp(buyer_max, seller_max, dispute,
admin_chat_last_seen) unchanged.
- Around line 125-146: The code currently swallows errors from
Order::get_startup_active_orders(...) and fetch_user_order_chat_updates(...).
Replace the terse patterns with proper error handling: when calling
Order::get_startup_active_orders(pool).await, match the Result and log the error
(e.g., tracing::error! or log::error!) with contextual info instead of just
returning; when calling fetch_user_order_chat_updates(client, pool,
&app.order_chat_last_seen).await, handle the Result explicitly (match/if let
Err(e)) and log the error before falling back to default updates so startup
continues; keep existing behavior of loading chats via
load_order_chat_from_file(&order_id) but ensure any IO errors there are
surfaced/logged by that function if applicable. Use the function names
Order::get_startup_active_orders and fetch_user_order_chat_updates in your
changes so the edits are easy to find.
In `@src/ui/tabs/order_in_progress_tab.rs`:
- Around line 117-149: Duplicate wrap_text_to_lines implementation should be
removed and the existing helper reused: change this file to call the central
wrap_text_to_lines function from the chat_render module (e.g. use
crate::ui::helpers::chat_render::wrap_text_to_lines) instead of redefining it;
if the helper in chat_render.rs is not public, make it public (pub or pub(crate)
as appropriate) and update the use/import in order_in_progress_tab.rs so the
single shared function is used. Ensure tests/build still pass after adjusting
visibility and remove the local wrap_text_to_lines function definition.
🪄 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: aa175745-adcd-4b31-8f2e-d17f46893f1c
📒 Files selected for processing (35)
docs/ADMIN_DISPUTES.mddocs/DATABASE.mddocs/MESSAGE_FLOW_AND_PROTOCOL.mddocs/STARTUP_AND_CONFIG.mddocs/TUI_INTERFACE.mdsrc/db.rssrc/main.rssrc/models.rssrc/ui/app_state.rssrc/ui/chat.rssrc/ui/constants.rssrc/ui/draw.rssrc/ui/helpers.rssrc/ui/helpers/attachments.rssrc/ui/helpers/chat_render.rssrc/ui/helpers/chat_storage.rssrc/ui/helpers/chat_visibility.rssrc/ui/helpers/formatting.rssrc/ui/helpers/layout.rssrc/ui/helpers/mod.rssrc/ui/helpers/startup.rssrc/ui/key_handler/async_tasks.rssrc/ui/key_handler/enter_handlers.rssrc/ui/key_handler/mod.rssrc/ui/key_handler/navigation.rssrc/ui/mod.rssrc/ui/navigation.rssrc/ui/state.rssrc/ui/tabs/mod.rssrc/ui/tabs/order_in_progress_tab.rssrc/util/chat_utils.rssrc/util/dm_utils/mod.rssrc/util/order_utils/fetch_scheduler.rssrc/util/order_utils/mod.rstests/common/mod.rs
💤 Files with no reviewable changes (1)
- src/ui/helpers.rs
| } else if let Tab::User(UserTab::MyTrades) = app.active_tab { | ||
| let order_ids: std::collections::HashSet<String> = match app.messages.lock() { | ||
| Ok(g) => g | ||
| .iter() | ||
| .filter_map(|m| m.order_id.map(|id| id.to_string())) | ||
| .collect(), | ||
| Err(_) => std::collections::HashSet::new(), | ||
| }; | ||
| if !order_ids.is_empty() && app.selected_order_chat_idx > 0 { | ||
| app.selected_order_chat_idx -= 1; | ||
| } |
There was a problem hiding this comment.
Use the rendered active-order list to bound My Trades navigation.
These branches derive limits from raw app.messages (unique IDs on one path, raw message count on the other), but order_in_progress_tab.rs renders a deduped, status-filtered active-order list. Once an order has multiple messages or a completed trade still exists in app.messages, selected_order_chat_idx can drift away from the visible sidebar and Up/Down no longer tracks the highlighted row correctly. Please compute the bounds from the same active-order projection the tab renders.
Also applies to: 389-396
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/ui/key_handler/navigation.rs` around lines 239 - 249, The navigation
logic for Tab::User(UserTab::MyTrades) currently bounds selected_order_chat_idx
using raw app.messages (and a HashSet of order_ids), causing index drift versus
the rendered list; update both this branch and the same logic at the other site
(around the 389-396 region) to compute the visible/deduped, status-filtered
active-order list using the same projection/function used by
order_in_progress_tab.rs (the active-order rendering helper) and then
clamp/adjust app.selected_order_chat_idx against that rendered list's length
instead of app.messages; reference Tab::User(UserTab::MyTrades), app.messages,
app.selected_order_chat_idx, and the active-order projection in
order_in_progress_tab.rs to locate and replace the bounds calculation.
| let last_seen = order_chat_last_seen | ||
| .get(&row.id) | ||
| .and_then(|s| s.last_seen_timestamp) | ||
| .unwrap_or(0); | ||
| let mut messages = fetch_gift_wraps_for_shared_key(client, &shared_keys).await?; | ||
| messages.retain(|(_, ts, _)| *ts >= last_seen); |
There was a problem hiding this comment.
Minor: >= comparison may re-process messages at exactly last_seen timestamp.
Line 423 uses *ts >= last_seen which would include messages with timestamp equal to the last seen value. If the same message is received again (e.g., from a different relay), it could be processed twice. Consider using > last_seen for stricter deduplication, or ensure downstream processing handles duplicates.
Suggested fix
- messages.retain(|(_, ts, _)| *ts >= last_seen);
+ messages.retain(|(_, ts, _)| *ts > last_seen);📝 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.
| let last_seen = order_chat_last_seen | |
| .get(&row.id) | |
| .and_then(|s| s.last_seen_timestamp) | |
| .unwrap_or(0); | |
| let mut messages = fetch_gift_wraps_for_shared_key(client, &shared_keys).await?; | |
| messages.retain(|(_, ts, _)| *ts >= last_seen); | |
| let last_seen = order_chat_last_seen | |
| .get(&row.id) | |
| .and_then(|s| s.last_seen_timestamp) | |
| .unwrap_or(0); | |
| let mut messages = fetch_gift_wraps_for_shared_key(client, &shared_keys).await?; | |
| messages.retain(|(_, ts, _)| *ts > last_seen); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/util/chat_utils.rs` around lines 418 - 423, The current filtering uses
messages.retain(|(_, ts, _)| *ts >= last_seen) which will include messages whose
timestamp equals order_chat_last_seen.last_seen_timestamp and can cause
re-processing duplicates; change the predicate to strictly greater (use >
last_seen) when filtering the vector returned by fetch_gift_wraps_for_shared_key
so messages.retain(...) only keeps entries with timestamp > last_seen (or
alternatively de-duplicate downstream), updating the call site where messages is
filtered to reference order_chat_last_seen and last_seen_timestamp accordingly.
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/ui/key_handler/message_handlers.rs (1)
43-52: Optional cleanup: remove duplicateddefault_moderecomputation.Line 47-Line 50 re-derives
default_modeeven though it is already computed at Line 18-Line 21.♻️ Proposed simplification
let Some(order_id) = view_state.order_id else { let _ = ctx .order_result_tx .send(OperationResult::Error("No order ID in message".to_string())); - let default_mode = match app.user_role { - UserRole::User => UiMode::UserMode(UserMode::Normal), - UserRole::Admin => UiMode::AdminMode(AdminMode::Normal), - }; app.mode = default_mode; return; };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/ui/key_handler/message_handlers.rs` around lines 43 - 52, The code duplicates the derivation of default_mode when view_state.order_id is None; instead reuse the earlier computed default_mode rather than recomputing it in the None branch—remove the second match on app.user_role that creates UiMode::UserMode(UserMode::Normal) / UiMode::AdminMode(AdminMode::Normal) and simply assign app.mode = default_mode after sending OperationResult::Error via ctx.order_result_tx, preserving the send of "No order ID in message" and the early return behavior around view_state.order_id.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@src/ui/key_handler/message_handlers.rs`:
- Around line 43-52: The code duplicates the derivation of default_mode when
view_state.order_id is None; instead reuse the earlier computed default_mode
rather than recomputing it in the None branch—remove the second match on
app.user_role that creates UiMode::UserMode(UserMode::Normal) /
UiMode::AdminMode(AdminMode::Normal) and simply assign app.mode = default_mode
after sending OperationResult::Error via ctx.order_result_tx, preserving the
send of "No order ID in message" and the early return behavior around
view_state.order_id.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: c29d1258-1264-4790-bb3c-8059a0363d50
📒 Files selected for processing (2)
src/ui/key_handler/esc_handlers.rssrc/ui/key_handler/message_handlers.rs
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (1)
src/ui/tabs/order_in_progress_tab.rs (1)
200-249: Consider avoiding full clone of messages under lock.Line 202-203 clones the entire messages vector while holding the lock. For large message sets, this could cause brief UI stutters. Consider iterating once to extract only the needed data, or using a more targeted snapshot.
This is a minor performance consideration and may be acceptable for typical message counts.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/ui/tabs/order_in_progress_tab.rs` around lines 200 - 249, The render_order_in_progress function currently clones the entire messages vector while holding app.messages lock (messages_snapshot = match app.messages.lock() ... g.clone()), which can cause UI stutter for large message sets; instead, inside render_order_in_progress take the mutex briefly and create a minimal snapshot of only the data needed by build_active_orders (e.g., map each message to an OrderSummary or the specific fields build_active_orders requires) or otherwise copy only identifiers/timestamps/text rather than the full message structs, then drop the lock and call build_active_orders on that lightweight snapshot (refer to app.messages, messages_snapshot, render_order_in_progress, and build_active_orders to locate and update the logic).
🤖 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/key_handler/chat_helpers.rs`:
- Around line 120-129: resolve_selected_mytrades_order_id currently iterates
HashMap keys in arbitrary order which mismatches the UI sorting in
order_in_progress_tab.rs (rows.sort_by(|a,b| a.order_id.cmp(&b.order_id))) and
causes wrong order actions; fix by collecting the keys from
app.active_order_trade_indices inside resolve_selected_mytrades_order_id and
sort them the same way the UI sorts (by order_id using Uuid ordering) before
indexing with app.selected_order_chat_idx, or switch active_order_trade_indices
to an ordered map type (IndexMap/BTreeMap) so that key iteration matches the UI
ordering.
In `@src/ui/key_handler/enter_handlers.rs`:
- Around line 127-143: The status filter used to build active_orders currently
only includes Active, FiatSent, and SettledHoldInvoice, causing a mismatch with
the sidebar; update the filter within the block that builds active_orders
(iterating over orders_lock) to include the same statuses used by
is_order_chat_actionable—add WaitingPayment, WaitingBuyerInvoice, and InProgress
alongside Active, FiatSent, and SettledHoldInvoice—so that
active_orders.get(app.selected_order_chat_idx) and the mapping to
OrderChatTarget(order_id) behaves consistently with the sidebar selection logic.
In `@src/ui/tabs/order_in_progress_tab.rs`:
- Around line 271-280: The footer constraint in the vertical Layout created in
main_chunks currently reserves only Constraint::Length(1), which is too small
when footer_line2 is Some; update the layout so the last constraint becomes
Constraint::Length(2) whenever footer_line2.is_some() and there is enough
terminal height (compute available height from main_area.height or otherwise)
before calling Layout::new(). Adjust the logic around main_chunks (the
Layout::new call and subsequent split) to calculate footer height first (or
conditionally build the constraints array) so that the footer rendering code
that splits into two rows can safely use two Length(1) rows without clipping.
- Around line 53-117: The sidebar's build_active_orders and the Enter handler's
resolve_selected_order_chat_target use different predicates which can cause
index mismatches; extract a single shared predicate/function (e.g.,
is_active_order or build_active_order_list) and use it in both places: replace
the inline filtering in build_active_orders and the filter in
resolve_selected_order_chat_target to call the shared function, ensure both use
the same ordering/sort (the .sort_by(|a,b| a.order_id.cmp(&b.order_id)) logic)
and identical construction of OrderChatListItem so selected_order_chat_idx
refers to the same list in both UI and Enter handler.
---
Nitpick comments:
In `@src/ui/tabs/order_in_progress_tab.rs`:
- Around line 200-249: The render_order_in_progress function currently clones
the entire messages vector while holding app.messages lock (messages_snapshot =
match app.messages.lock() ... g.clone()), which can cause UI stutter for large
message sets; instead, inside render_order_in_progress take the mutex briefly
and create a minimal snapshot of only the data needed by build_active_orders
(e.g., map each message to an OrderSummary or the specific fields
build_active_orders requires) or otherwise copy only identifiers/timestamps/text
rather than the full message structs, then drop the lock and call
build_active_orders on that lightweight snapshot (refer to app.messages,
messages_snapshot, render_order_in_progress, and build_active_orders to locate
and update the logic).
🪄 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: a12b0ef5-805c-4302-87ac-fddcd168db92
📒 Files selected for processing (15)
docs/DATABASE.mdsrc/ui/constants.rssrc/ui/draw.rssrc/ui/help_popup.rssrc/ui/helpers/formatting.rssrc/ui/helpers/startup.rssrc/ui/key_handler/admin_handlers.rssrc/ui/key_handler/async_tasks.rssrc/ui/key_handler/chat_helpers.rssrc/ui/key_handler/enter_handlers.rssrc/ui/key_handler/input_helpers.rssrc/ui/key_handler/message_handlers.rssrc/ui/key_handler/mod.rssrc/ui/tabs/order_in_progress_tab.rssrc/ui/tabs/tab_content.rs
✅ Files skipped from review due to trivial changes (1)
- src/ui/key_handler/admin_handlers.rs
🚧 Files skipped from review as they are similar to previous changes (6)
- src/ui/key_handler/message_handlers.rs
- src/ui/key_handler/async_tasks.rs
- docs/DATABASE.md
- src/ui/helpers/formatting.rs
- src/ui/constants.rs
- src/ui/helpers/startup.rs
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
src/ui/tabs/order_in_progress_tab.rs (1)
269-278:⚠️ Potential issue | 🟡 MinorFooter area is too small for two-line footer layout.
The layout allocates
Constraint::Length(1)for the footer area (line 275), but whenfooter_line2isSome(wide terminals ≥90 chars), lines 525-531 attempt to split this single row into twoLength(1)rows. The second line will be clipped.🐛 Proposed fix for dynamic footer height
+ // Pre-calculate footer height before layout + let footer_height = if main_area.width >= 90 { 2 } else { 1 }; + let main_chunks = Layout::new( Direction::Vertical, [ Constraint::Length(7), Constraint::Min(0), Constraint::Length(3), - Constraint::Length(1), + Constraint::Length(footer_height), ], ) .split(main_area);Also applies to: 524-534
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/ui/tabs/order_in_progress_tab.rs` around lines 269 - 278, The footer is allocated only Constraint::Length(1) in the main_chunks layout but code later renders two footer lines when footer_line2 is Some, causing clipping; modify the layout creation (the Layout::new call that produces main_chunks) to compute footer_height dynamically (e.g., let footer_height = if footer_line2.is_some() { 2 } else { 1 }) and replace the trailing Constraint::Length(1) with Constraint::Length(footer_height) so the footer area grows to two rows when footer_line2 exists.
🧹 Nitpick comments (4)
src/ui/key_handler/enter_handlers.rs (2)
92-111: Redundant mode assignment on line 109.The
app.modeis already set tomode_after_sendon line 98, and sincereset_input(line 108) only clears input state without modifying the mode, the assignment on line 109 is redundant.♻️ Proposed simplification
app.mode = mode_after_send.clone(); if content.is_empty() || !input_enabled { return; } let Some(target) = resolve_target(app) else { return; }; apply_local(app, &target, &content); reset_input(app); - app.mode = mode_after_send; spawn_remote(target, content); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/ui/key_handler/enter_handlers.rs` around lines 92 - 111, The code sets app.mode = mode_after_send twice: once just after destructuring EnterChatSendConfig and again after reset_input; remove the redundant assignment after reset_input (the one immediately before spawn_remote) since reset_input does not alter app.mode; keep the initial app.mode = mode_after_send (which uses mode_after_send.clone()) and leave resolve_target, apply_local, reset_input, and spawn_remote unchanged.
162-182: Add warning logs for silent early returns.The function logs warnings for some failures (order not found, send failure) but silently returns for missing
trade_keys(line 168) and missingshared_keys(line 181). This makes debugging difficult when chat messages fail to send.♻️ Proposed fix to add warning logs
let trade_sk = match order .trade_keys .as_deref() .and_then(|h| SecretKey::from_str(h).ok()) { Some(sk) => sk, - None => return, + None => { + log::warn!("order chat send skipped (missing or invalid trade_keys)"); + return; + } }; let trade_keys = Keys::new(trade_sk); let shared_keys = order .order_chat_shared_key_hex .as_deref() .and_then(crate::util::chat_utils::keys_from_shared_hex) .or_else(|| { let cp = order.counterparty_pubkey.as_deref()?; let pk = PublicKey::parse(cp).ok()?; crate::util::chat_utils::derive_shared_keys(Some(&trade_keys), Some(&pk)) }); let Some(shared_keys) = shared_keys else { + log::warn!("order chat send skipped (could not derive shared keys)"); return; };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/ui/key_handler/enter_handlers.rs` around lines 162 - 182, The code silently returns when trade key parsing and shared key derivation fail; add warning logs at both failure points to aid debugging. Specifically, where SecretKey::from_str(...) is matched (the block producing trade_sk / returning None) add a warn! (or the project's logger) logging that trade_keys parsing failed and include identifying info from order (e.g., order id or order.uid). Likewise, before the early return when shared_keys is None (the `let Some(shared_keys) = shared_keys else { return; }` branch), log a warn! indicating shared key derivation failed (include whether order_chat_shared_key_hex was missing and/or counterparty_pubkey used and the order identifier). Use the same logger style as other warnings in this file so messages are consistent.src/ui/key_handler/chat_helpers.rs (1)
120-129: Consider consistent error handling for poisoned locks.This function silently returns
Nonewhen the lock is poisoned (via.ok()), while similar lock acquisitions elsewhere (e.g.,resolve_selected_order_chat_targetinenter_handlers.rs) callrequest_fatal_restart. This inconsistency could mask serious state corruption issues.♻️ Proposed fix for consistent error handling
pub fn resolve_selected_mytrades_order_id(app: &AppState) -> Option<Uuid> { - app.active_order_trade_indices.lock().ok().and_then(|map| { + let map = match app.active_order_trade_indices.lock() { + Ok(g) => g, + Err(e) => { + crate::util::request_fatal_restart(format!( + "Mostrix encountered an internal error (poisoned active_order_trade_indices lock: {e}). Please restart the app." + )); + return None; + } + }; - // HashMap iteration order is arbitrary; sort keys to match the - // stable ordering used in the MyTrades sidebar (by order_id UUID). - let mut order_ids: Vec<Uuid> = map.keys().copied().collect(); - order_ids.sort(); - order_ids.get(app.selected_order_chat_idx).copied() - }) + // HashMap iteration order is arbitrary; sort keys to match the + // stable ordering used in the MyTrades sidebar (by order_id UUID). + let mut order_ids: Vec<Uuid> = map.keys().copied().collect(); + order_ids.sort(); + order_ids.get(app.selected_order_chat_idx).copied() }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/ui/key_handler/chat_helpers.rs` around lines 120 - 129, The function resolve_selected_mytrades_order_id currently swallows poisoned lock errors by calling .ok(); change it to mirror resolve_selected_order_chat_target by detecting a poisoned lock on app.active_order_trade_indices and invoking request_fatal_restart with a descriptive context instead of returning None, then proceed to lock and compute the sorted order_ids and selected UUID as before; reference the resolve_selected_mytrades_order_id function and the AppState.active_order_trade_indices field and use the existing request_fatal_restart helper to handle the poison case.src/ui/tabs/order_in_progress_tab.rs (1)
199-202: Silent failure on lock error.Similar to other lock handling in the codebase, a poisoned lock typically indicates serious state corruption. Consider using
request_fatal_restartfor consistency, or at minimum log the error.♻️ Proposed fix for consistent error handling
let messages_snapshot = match app.messages.lock() { Ok(g) => g.clone(), - Err(_) => Vec::new(), + Err(e) => { + log::error!("Failed to lock messages: {}", e); + Vec::new() + } };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/ui/tabs/order_in_progress_tab.rs` around lines 199 - 202, The match on app.messages.lock() silently drops errors into an empty Vec, but a poisoned lock should be handled consistently (e.g., call request_fatal_restart or at least log). Replace the Err(_) arm in the messages lock match used to populate messages_snapshot with a call to request_fatal_restart (or process logger) passing the lock error context and a brief message, so that when app.messages.lock() fails the app triggers the same fatal-restart path used elsewhere instead of silently continuing; keep the Ok(g) => g.clone() arm unchanged and ensure the request_fatal_restart reference and any captured error are included for debugging.
🤖 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/order_in_progress_tab.rs`:
- Around line 41-49: The matches! invocation in is_order_chat_actionable has a
malformed leading `|` and incorrect grouping; replace the pattern list with a
proper matches! pattern matching any of the variants. Specifically, edit
function is_order_chat_actionable to use matches!(status,
Some(Status::SettledHoldInvoice) | Some(Status::Active) | Some(Status::FiatSent)
| Some(Status::Success)) so the patterns are separated by `|` without a stray
`|` at the start and each variant is referenced as Some(Status::...).
---
Duplicate comments:
In `@src/ui/tabs/order_in_progress_tab.rs`:
- Around line 269-278: The footer is allocated only Constraint::Length(1) in the
main_chunks layout but code later renders two footer lines when footer_line2 is
Some, causing clipping; modify the layout creation (the Layout::new call that
produces main_chunks) to compute footer_height dynamically (e.g., let
footer_height = if footer_line2.is_some() { 2 } else { 1 }) and replace the
trailing Constraint::Length(1) with Constraint::Length(footer_height) so the
footer area grows to two rows when footer_line2 exists.
---
Nitpick comments:
In `@src/ui/key_handler/chat_helpers.rs`:
- Around line 120-129: The function resolve_selected_mytrades_order_id currently
swallows poisoned lock errors by calling .ok(); change it to mirror
resolve_selected_order_chat_target by detecting a poisoned lock on
app.active_order_trade_indices and invoking request_fatal_restart with a
descriptive context instead of returning None, then proceed to lock and compute
the sorted order_ids and selected UUID as before; reference the
resolve_selected_mytrades_order_id function and the
AppState.active_order_trade_indices field and use the existing
request_fatal_restart helper to handle the poison case.
In `@src/ui/key_handler/enter_handlers.rs`:
- Around line 92-111: The code sets app.mode = mode_after_send twice: once just
after destructuring EnterChatSendConfig and again after reset_input; remove the
redundant assignment after reset_input (the one immediately before spawn_remote)
since reset_input does not alter app.mode; keep the initial app.mode =
mode_after_send (which uses mode_after_send.clone()) and leave resolve_target,
apply_local, reset_input, and spawn_remote unchanged.
- Around line 162-182: The code silently returns when trade key parsing and
shared key derivation fail; add warning logs at both failure points to aid
debugging. Specifically, where SecretKey::from_str(...) is matched (the block
producing trade_sk / returning None) add a warn! (or the project's logger)
logging that trade_keys parsing failed and include identifying info from order
(e.g., order id or order.uid). Likewise, before the early return when
shared_keys is None (the `let Some(shared_keys) = shared_keys else { return; }`
branch), log a warn! indicating shared key derivation failed (include whether
order_chat_shared_key_hex was missing and/or counterparty_pubkey used and the
order identifier). Use the same logger style as other warnings in this file so
messages are consistent.
In `@src/ui/tabs/order_in_progress_tab.rs`:
- Around line 199-202: The match on app.messages.lock() silently drops errors
into an empty Vec, but a poisoned lock should be handled consistently (e.g.,
call request_fatal_restart or at least log). Replace the Err(_) arm in the
messages lock match used to populate messages_snapshot with a call to
request_fatal_restart (or process logger) passing the lock error context and a
brief message, so that when app.messages.lock() fails the app triggers the same
fatal-restart path used elsewhere instead of silently continuing; keep the Ok(g)
=> g.clone() arm unchanged and ensure the request_fatal_restart reference and
any captured error are included for debugging.
🪄 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: d9404afa-a9eb-418d-8f2c-35524dc31474
📒 Files selected for processing (4)
src/main.rssrc/ui/key_handler/chat_helpers.rssrc/ui/key_handler/enter_handlers.rssrc/ui/tabs/order_in_progress_tab.rs
🚧 Files skipped from review as they are similar to previous changes (1)
- src/main.rs
There was a problem hiding this comment.
Thanks for the cleanup here, but I don't think this is merge-ready yet.
The main issue I still see is in the My Trades order-chat selection path:
render_order_in_progressbuilds the sidebar fromapp.messagesviabuild_active_orders(...)resolve_selected_order_chat_targetbuilds a different list fromctx.orders- the two sources do not guarantee the same membership or ordering
That means selected_order_chat_idx can point to one row in the sidebar while Enter resolves a different order (or no order at all). This is exactly the kind of UI/action desync that can make the user send a chat message or trigger an action on the wrong trade, which is not acceptable.
Please make both the sidebar and the Enter/action handlers derive the selected order from the same shared projection, with the same filter and the same ordering.
I am also not comfortable approving while checks are still running.
Use one shared projection for the My Trades sidebar and Enter/send handlers to prevent selection/action desync.
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
docs/TUI_INTERFACE.md (1)
52-66:⚠️ Potential issue | 🟡 MinorFooter description contradicts the embedded
src/ui/mod.rssnippet.Line 66 now describes the footer as a "3-line Footer / Status bar", but the code snippet right above (Lines 52–62) still shows
Constraint::Length(1)for that chunk. Either the top-level layout was bumped to 3 lines (and the snippet is stale) or the status bar is still 1 line and this prose is wrong. Please reconcile so the snippet and the description agree.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/TUI_INTERFACE.md` around lines 52 - 66, The docs claim the footer is "3 lines" but the UI layout snippet shows Constraint::Length(1) for the footer in the Layout::new(Direction::Vertical, [...]) split; reconcile by either updating the code in src/ui/mod.rs (change the footer chunk from Constraint::Length(1) to Constraint::Length(3) in the Layout::new call) if the footer was expanded, or update the prose in TUI_INTERFACE.md to describe a 1-line footer/status bar to match the current Constraint::Length(1); reference the Layout::new, Direction::Vertical, and Constraint::Length(...) symbols when making the change.
🧹 Nitpick comments (1)
src/ui/helpers/order_chat_projection.rs (1)
98-103: Sidebar ordering is lexicographic byorder_id, not time-based.Sorting by the UUID string yields a stable order, but it isn't chronological, so newly opened trades can appear in the middle of the sidebar rather than at the top/bottom. Since
build_active_order_chat_listalready capturescreated_at, consider sorting bycreated_at(withorder_idas tiebreaker) for a more intuitive "My Trades" list. Stability of ordering — which is what matters forselected_order_chat_idxalignment — is preserved either way because both the sidebar and Enter handler consume the same projection.♻️ Optional: sort by created_at desc, fallback to order_id
- rows.sort_by(|a, b| a.order_id.cmp(&b.order_id)); + rows.sort_by(|a, b| { + b.created_at + .cmp(&a.created_at) + .then_with(|| a.order_id.cmp(&b.order_id)) + });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/ui/helpers/order_chat_projection.rs` around lines 98 - 103, The sidebar is currently sorted lexicographically by order_id; change the final sort in build_active_order_chat_list to sort by created_at (newest first) and use order_id as a deterministic tiebreaker so actionable rows (filtered via is_order_chat_actionable) are ordered chronologically while remaining stable for selected_order_chat_idx; replace the rows.sort_by(...) call with a comparator that first compares b.created_at.cmp(&a.created_at) for descending created_at and falls back to a.order_id.cmp(&b.order_id).
🤖 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/order_in_progress_tab.rs`:
- Around line 289-299: The UI currently renders hardcoded "Unknown" strings in
the Span::styled/Line::from block for "Privacy", "Buyer Rating", and "Seller
Rating"; update the code that builds these spans to use the actual order fields
(e.g., order.privacy, order.buyer_rating, order.seller_rating) and format them
into the Span::styled instead of the literal "Unknown", or if those fields are
Option/nullable, conditionally omit the entire Line (or show a localized
placeholder only when None) to avoid shipping static placeholders; if the data
wiring isn't implemented yet, add a TODO comment next to this
Span::styled/Line::from block mentioning the missing wiring and ensure you
reference the exact labels ("Privacy:", "Buyer Rating:", "Seller Rating:") when
making the change.
- Around line 24-56: wrap_text_to_lines currently leaves words longer than
max_width intact, causing overflow; update wrap_text_to_lines to split any word
whose character count (use word.chars().count() not byte len()) exceeds
max_width into contiguous chunks of at most max_width characters and append
those chunks into wrapped (respecting current: fill current with a partial chunk
if space remains, otherwise push current and start a new chunk), ensure you
still handle the existing logic for joining words with spaces and the max_width
== 0 early return, and reference variables/functions: wrap_text_to_lines,
content, max_width, word, current, wrapped when making the change.
---
Outside diff comments:
In `@docs/TUI_INTERFACE.md`:
- Around line 52-66: The docs claim the footer is "3 lines" but the UI layout
snippet shows Constraint::Length(1) for the footer in the
Layout::new(Direction::Vertical, [...]) split; reconcile by either updating the
code in src/ui/mod.rs (change the footer chunk from Constraint::Length(1) to
Constraint::Length(3) in the Layout::new call) if the footer was expanded, or
update the prose in TUI_INTERFACE.md to describe a 1-line footer/status bar to
match the current Constraint::Length(1); reference the Layout::new,
Direction::Vertical, and Constraint::Length(...) symbols when making the change.
---
Nitpick comments:
In `@src/ui/helpers/order_chat_projection.rs`:
- Around line 98-103: The sidebar is currently sorted lexicographically by
order_id; change the final sort in build_active_order_chat_list to sort by
created_at (newest first) and use order_id as a deterministic tiebreaker so
actionable rows (filtered via is_order_chat_actionable) are ordered
chronologically while remaining stable for selected_order_chat_idx; replace the
rows.sort_by(...) call with a comparator that first compares
b.created_at.cmp(&a.created_at) for descending created_at and falls back to
a.order_id.cmp(&b.order_id).
🪄 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: 5d480013-b200-4a81-be33-0fbaee176dc1
📒 Files selected for processing (8)
docs/ADMIN_DISPUTES.mddocs/MESSAGE_FLOW_AND_PROTOCOL.mddocs/SETTINGS_ANALYSIS.mddocs/TUI_INTERFACE.mdsrc/ui/helpers/mod.rssrc/ui/helpers/order_chat_projection.rssrc/ui/key_handler/enter_handlers.rssrc/ui/tabs/order_in_progress_tab.rs
✅ Files skipped from review due to trivial changes (2)
- docs/SETTINGS_ANALYSIS.md
- docs/MESSAGE_FLOW_AND_PROTOCOL.md
🚧 Files skipped from review as they are similar to previous changes (3)
- docs/ADMIN_DISPUTES.md
- src/ui/helpers/mod.rs
- src/ui/key_handler/enter_handlers.rs
There was a problem hiding this comment.
Thanks, this looks good now.
The main blocker from my previous review was the My Trades selection mismatch, where the sidebar and the Enter/action path were deriving the active order from different sources. That part is now fixed properly by routing both through the same shared projection (build_active_order_chat_list), so selected_order_chat_idx stays aligned with the highlighted row.
I also re-checked the latest state:
- the new shared projection is used consistently in the sidebar render path and in
resolve_selected_order_chat_target - checks are green
- the overall refactor is much easier to follow now that the old monolithic helper file has been split into focused modules
From my side, this is ready to merge.
Summary
src/ui/helpersinto focused modules (layout,formatting,chat_storage,chat_render,chat_visibility,attachments,startup) with a compatibility re-export layerSummary by CodeRabbit
New Features
Improvements
Database
Documentation