From 067ecbbbfe27e3f95cee32c9f23b68d4ab93d38c Mon Sep 17 00:00:00 2001 From: Aman Agrawal Date: Tue, 5 Aug 2025 18:04:18 +0530 Subject: [PATCH] inbox_ui: Fix scroll jump after initial render. It is possible for us to scroll user back to top of view if they scroll before the render is complete and we haven't called `revive_current_focus` yet. This fixes it by wrapping the render and revive logic in a single animation frame so that any scroll requests are delayed until we have completed both of them. --- web/src/inbox_ui.ts | 118 ++++++++++++++++++++++---------------------- 1 file changed, 60 insertions(+), 58 deletions(-) diff --git a/web/src/inbox_ui.ts b/web/src/inbox_ui.ts index aa5effc390..cbc0ae4875 100644 --- a/web/src/inbox_ui.ts +++ b/web/src/inbox_ui.ts @@ -1168,73 +1168,75 @@ export function complete_rerender(): void { } load_data_from_ls(); - let first_filter: IteratorResult; - if (inbox_util.is_channel_view()) { - const channel_id = inbox_util.get_channel_id(); - assert(channel_id !== undefined); + // To avoid user scrolling before we have completed the rendering, + // Wrap the rendering and position restoration in a requestAnimationFrame. + requestAnimationFrame(() => { + let first_filter: IteratorResult; + if (inbox_util.is_channel_view()) { + const channel_id = inbox_util.get_channel_id(); + assert(channel_id !== undefined); - if (channel_view_topic_widget?.get_stream_id() === channel_id) { - channel_view_topic_widget.build(); - } else { - // Show unknown channel message if we don't have data for channel. - if (!stream_data.get_sub_by_id(channel_id)) { - $("#inbox-pane").html( - render_inbox_view({ - unknown_channel: true, - }), - ); - return; + if (channel_view_topic_widget?.get_stream_id() === channel_id) { + channel_view_topic_widget.build(); + } else { + // Show unknown channel message if we don't have data for channel. + if (!stream_data.get_sub_by_id(channel_id)) { + $("#inbox-pane").html( + render_inbox_view({ + unknown_channel: true, + }), + ); + return; + } + + render_channel_view(channel_id); } - - render_channel_view(channel_id); + const channel_filter = per_channel_filters.get(channel_id) ?? new Set([DEFAULT_FILTER]); + first_filter = channel_filter.values().next(); + } else { + channel_view_topic_widget = undefined; + const {has_visible_unreads, ...additional_context} = reset_data(); + $("#inbox-pane").html( + render_inbox_view({ + normal_view: true, + search_val: search_keyword, + INBOX_SEARCH_ID, + dms_dict, + topics_dict, + streams_dict, + channel_folders_dict, + ...additional_context, + }), + ); + show_empty_inbox_channel_view_text(false); + show_empty_inbox_text(has_visible_unreads); + first_filter = filters.values().next(); } - const channel_filter = per_channel_filters.get(channel_id) ?? new Set([DEFAULT_FILTER]); - first_filter = channel_filter.values().next(); - } else { - channel_view_topic_widget = undefined; - const {has_visible_unreads, ...additional_context} = reset_data(); - $("#inbox-pane").html( - render_inbox_view({ - normal_view: true, - search_val: search_keyword, - INBOX_SEARCH_ID, - dms_dict, - topics_dict, - streams_dict, - channel_folders_dict, - ...additional_context, - }), - ); - show_empty_inbox_channel_view_text(false); - show_empty_inbox_text(has_visible_unreads); - first_filter = filters.values().next(); - } - // If the focus is not on the inbox rows, the inbox view scrolls - // down when moving from other views to the inbox view. To avoid - // this, we scroll to top before restoring focus via revive_current_focus. - if (!is_list_focused()) { - window.scrollTo(0, 0); - } else if (last_scroll_offset !== undefined) { - // It is important to restore the scroll position as soon - // as the rendering is complete to avoid scroll jumping. - window.scrollTo(0, last_scroll_offset); - } + // If the focus is not on the inbox rows, the inbox view scrolls + // down when moving from other views to the inbox view. To avoid + // this, we scroll to top before restoring focus via revive_current_focus. + if (!is_list_focused()) { + window.scrollTo(0, 0); + } else if (last_scroll_offset !== undefined) { + // It is important to restore the scroll position as soon + // as the rendering is complete to avoid scroll jumping. + window.scrollTo(0, last_scroll_offset); + } - setTimeout(() => { revive_current_focus(); is_waiting_for_revive_current_focus = false; - }, 0); - filters_dropdown_widget = new dropdown_widget.DropdownWidget({ - ...views_util.COMMON_DROPDOWN_WIDGET_PARAMS, - widget_name: "inbox-filter", - item_click_callback: filter_click_handler, - $events_container: $("#inbox-main"), - default_id: first_filter.done ? DEFAULT_FILTER : first_filter.value, - get_options: inbox_view_dropdown_options, + filters_dropdown_widget = new dropdown_widget.DropdownWidget({ + ...views_util.COMMON_DROPDOWN_WIDGET_PARAMS, + widget_name: "inbox-filter", + item_click_callback: filter_click_handler, + $events_container: $("#inbox-main"), + default_id: first_filter.done ? DEFAULT_FILTER : first_filter.value, + get_options: inbox_view_dropdown_options, + }); + filters_dropdown_widget.setup(); }); - filters_dropdown_widget.setup(); } export function search_and_update(): void {