Skip to content

Conversation

@ZhangHanDong
Copy link
Contributor

@ZhangHanDong ZhangHanDong commented Oct 22, 2025

Fixed issue #506

This is a remastered version of the PR #550 .

  • Add a lightweight cpu_worker thread that stays alive for the app lifetime and runs CPU-bound jobs (currently the room-member search); App::handle_startup initializes it and widgets enqueue work via cpu_worker::spawn_cpu_job.
  • Rework MentionableTextInput into an explicit state machine with search IDs, cancellation tokens, loading/empty UI states, and streaming updates that arrive over the background worker channel.
  • Replace the old MatrixRequest::SearchRoomMembers path: sliding_sync now fetches joined-member lists directly, emits RoomMembersListFetched, and RoomScreen caches those members plus precomputed sort data for downstream widgets.
  • Refactor room/member_search.rs to support batched streaming, cancellation, richer match heuristics, optional precomputed sort reuse, and add unit tests for the new helpers.

There are three related issues that still need to be fixed.

@ZhangHanDong ZhangHanDong added the waiting-on-review This issue is waiting to be reviewed label Oct 22, 2025
@kevinaboos
Copy link
Member

@alanpoon & @tyreseluo, can you please give this a review before I dig in? Thanks.

@tyreseluo
Copy link
Contributor

@alanpoon & @tyreseluo, can you please give this a review before I dig in? Thanks.

yep, i will.

…tartup so CPU-bound work like member searches runs off the UI thread.

  - Reworked mention search to enqueue jobs on the CPU worker with cancellation tokens, unique search IDs, and better handling of loading state when the member list is still fetching.
  - Updated room loading and sync flow to fetch the full member list from the homeserver and deliver it through a new RoomMembersListFetched timeline update, removing the old MatrixRequest::SearchRoomMembers path.
  - Refactored the member search algorithm for batching, cancellation, richer match strategies, and added unit tests for the new helpers.
@ZhangHanDong ZhangHanDong added waiting-on-author This issue is waiting on the original author for a response and removed waiting-on-review This issue is waiting to be reviewed labels Oct 25, 2025
@ZhangHanDong ZhangHanDong added waiting-on-review This issue is waiting to be reviewed and removed waiting-on-author This issue is waiting on the original author for a response labels Oct 26, 2025
@ZhangHanDong
Copy link
Contributor Author

Review suggestions:
• Select a large room (thousands or tens of thousands of members), @ a user, and search.
• Continue testing in rooms with more than 50 members.
• Continue testing in rooms with fewer than 50 members.
• Close large/small rooms, reopen them, and continue testing.
• Logout, then login again, and rerun the above four steps.

Copy link
Contributor

@alanpoon alanpoon left a comment

Choose a reason for hiding this comment

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

  1. Overall the performance of loading the mentionable list seems to be impacted.
  2. There's seem too many fields added MentionableTextInput.
  3. There's also bug when clicking the user from mentionable list, it does not apply into the text box.
  4. The search does not work after "comma".
searching_after comma 5. There should not be "Notify the Room" for direct message. Screenshot 2025-10-29 at 5 29 29 PM

Comment on lines 680 to 693
match timeline.room().members(RoomMemberships::JOIN).await {
Ok(members) => {
let count = members.len();
log!("Fetched {count} members for room {room_id} after sync.");
if let Err(err) = sender.send(TimelineUpdate::RoomMembersListFetched { members }) {
warning!("Failed to send RoomMembersListFetched update for room {room_id}: {err:?}");
} else {
SignalToUI::set_ui_signal();
}
}
Err(err) => {
warning!("Failed to fetch room members from server for room {room_id}: {err:?}");
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

There is already timeline.fetched_members above. This looks like a redundant call. There is extra matrix sqlite call. This could explain why this PR is slower when loading the mentionable list.
Screenshot 2025-10-29 at 1 13 24 PM It looks like the mentionable list still loads even this change is reverted.

sender: &Sender<SearchResult>,
cancel_token: &Option<Arc<AtomicBool>>,
search_id: u64,
search_text: &Arc<String>,
Copy link
Contributor

Choose a reason for hiding this comment

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

No need for Arc

if self.is_searching() {
if let Event::KeyUp(key_event) = event {
if key_event.key_code == KeyCode::Escape {
self.cancel_active_search();
Copy link
Contributor

Choose a reason for hiding this comment

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

After pressing KeyCode::Escaope, it is not cancelled. is_cancelled does not return true,

Copy link
Contributor

Choose a reason for hiding this comment

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

Screen.Recording.2025-11-28.at.10.54.10.PM.mov
  1. Clear the cache.
  2. Login
  3. Go to Matrix HQ room
  4. Quickly type "@", ensure it shows "Loading"
  5. Quickly press "ESC"
  6. The popup closes, but reopens after search result returns.

This should show that is_cancelled will never return "True.

Comment on lines +454 to +456
// ESC key is now handled in the main event handler using KeyUp event
// This avoids conflicts with escaped() method being consumed by other components

Copy link
Contributor

Choose a reason for hiding this comment

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

can be removed

// Force a fresh search now that members are available
let search_text = self.cmd_text_input.search_text();
self.last_search_text = None;
self.update_user_list(cx, &search_text, scope);
Copy link
Contributor

Choose a reason for hiding this comment

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

There is a cyclic dependencies. MentionableTextInputAction::RoomMembersLoaded -> "update_user_list" -> submit_async_request(MatrixRequest::GetRoomMembers) -> TimelineUpdate::RoomMembersListFetched -> MentionableTextInputAction::RoomMembersLoaded

Comment on lines +1084 to +1097
if is_complete {
self.search_results_pending = false;
// Search is complete - get results for final UI update
let final_results = if let MentionSearchState::Searching {
accumulated_results,
..
} = &self.search_state
{
accumulated_results.clone()
} else {
Vec::new()
};

if final_results.is_empty() {
Copy link
Contributor

Choose a reason for hiding this comment

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

For some reason, when it is loading, the search result is complete but is final results is empty. Hence result the overall code has several checks.
Screenshot 2025-10-29 at 4 13 32 PM

@ZhangHanDong ZhangHanDong added waiting-on-author This issue is waiting on the original author for a response and removed waiting-on-review This issue is waiting to be reviewed labels Oct 30, 2025
@ZhangHanDong
Copy link
Contributor Author

ZhangHanDong commented Nov 26, 2025

@kevinaboos

Current PR progress:

  1. Fix the issue where remote sync of members does not work after logout-login
    - Previously, after logging out and then logging back in, remote synchronization of room members failed to trigger properly
    - The relevant state reset logic has been corrected
  2. Remove the behavior of automatically deleting the local members cache after closing a tab
    - This behavior is considered unnecessary and harms the user experience
    - In the future, a setting can be added to manually clear local cached data
  3. Optimize the member list loading experience
    - Automatically check local cached
    members; if a cache exists, render the user list immediately without waiting for remote data
    - Each room performs remote synchronization only once per application session
    - During synchronization, show a loading
    animation at the bottom of the user list; it will disappear automatically after sync completes
  4. Fix the issue where the cursor disappears after clicking to select a mention user with the mouse
    - Previously, after clicking to select a user in the mention
    popup, the cursor would blink and then disappear, making it impossible to continue typing
    - Resolved by retrying the focus restoration mechanism in draw_walk
  5. Fix duplicate room tabs after app restart or logout-login
    - Previously, restoring rooms could create duplicate tabs
    - Avoided by looking up existing tabs based on room_id

Remote sync room_members trigger conditions:

Remote member synchronization (SyncRoomMemberList) is triggered in the following cases:

  • First time opening a room after the application starts
  • Login again after Logout
  • Switching to a new room that has never been loaded

Note: Within the same application session, closing a room tab
and reopening it will not trigger remote synchronization again.


The current code still needs another review; functional testing can proceed first.

Comment on lines +1239 to +1244
accumulated_results.clone()
} else {
Vec::new()
};

// Clear old list items, prepare to populate new list
self.cmd_text_input.clear_items();
if !results_for_ui.is_empty() {
Copy link
Contributor

Choose a reason for hiding this comment

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

No need to clone the accumulated_results just to get the length for the results for ui.

Comment on lines +1263 to +1268
accumulated_results.clone()
} else {
Vec::new()
};

if final_results.is_empty() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Same as above

Comment on lines +1334 to +1340
accumulated_results.clone()
} else {
Vec::new()
};

// Add user mention items using the results
if !results_to_display.is_empty() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Same as above

Comment on lines +1290 to +1294
if disconnected {
// Channel was closed - search completed or failed
self.search_results_pending = false;
self.handle_search_channel_closed(cx, scope);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Should it return "False" when the channel is closed? It is still check_search_channel even after search_channel_closed is closed.

if self.is_searching() {
if let Event::KeyUp(key_event) = event {
if key_event.key_code == KeyCode::Escape {
self.cancel_active_search();
Copy link
Contributor

Choose a reason for hiding this comment

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

Screen.Recording.2025-11-28.at.10.54.10.PM.mov
  1. Clear the cache.
  2. Login
  3. Go to Matrix HQ room
  4. Quickly type "@", ensure it shows "Loading"
  5. Quickly press "ESC"
  6. The popup closes, but reopens after search result returns.

This should show that is_cancelled will never return "True.

Comment on lines 3568 to +3572
pub async fn clear_app_state(config: &LogoutConfig) -> Result<()> {
// Get the database path before clearing CLIENT
// We need this to delete the Matrix SDK database after logout
let db_path_to_delete = if let Some(user_id) = current_user_id() {
use crate::persistence::session_file_path;
Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure how deleting the Matrix SDK database after logout is related to this PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

waiting-on-author This issue is waiting on the original author for a response

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants