From 7bcc5280644ddc4772b99291a9adada3e2d4f3f9 Mon Sep 17 00:00:00 2001 From: Fabian Freund Date: Sun, 9 Aug 2026 02:41:58 +0200 Subject: [PATCH] fix tab navigation order --- .../geckoview/domain/repositories/tab.dart | 111 ++++++-- .../geckoview/domain/repositories/tab.g.dart | 2 +- .../features/browser/domain/providers.dart | 82 ++++++ .../features/browser/domain/providers.g.dart | 268 ++++++++++++++++++ .../browser_modules/bottom_app_bar.dart | 7 +- .../widgets/tab_view/tab_grid_view.dart | 15 +- .../widgets/tab_view/tab_list_view.dart | 15 +- 7 files changed, 449 insertions(+), 51 deletions(-) diff --git a/apps/weblibre/lib/features/geckoview/domain/repositories/tab.dart b/apps/weblibre/lib/features/geckoview/domain/repositories/tab.dart index 79490027..e7093a03 100644 --- a/apps/weblibre/lib/features/geckoview/domain/repositories/tab.dart +++ b/apps/weblibre/lib/features/geckoview/domain/repositories/tab.dart @@ -39,6 +39,7 @@ import 'package:weblibre/features/geckoview/domain/providers/tab_detail_state.da import 'package:weblibre/features/geckoview/domain/providers/tab_list.dart'; import 'package:weblibre/features/geckoview/domain/providers/tab_state.dart'; import 'package:weblibre/features/geckoview/features/browser/domain/controllers/home_target_controller.dart'; +import 'package:weblibre/features/geckoview/features/browser/domain/providers.dart'; import 'package:weblibre/features/geckoview/features/browser/domain/services/browser_data.dart'; import 'package:weblibre/features/geckoview/features/tabs/data/database/database.dart'; import 'package:weblibre/features/geckoview/features/tabs/data/entities/isolation_context.dart'; @@ -446,35 +447,83 @@ class TabRepository extends _$TabRepository { String tabId, { String? containerId, bool skipContainerCheck = true, - }) async { - final previousTabId = await _adjacentVisibleTabByOrder( - tabId, - containerId: containerId, - skipContainerCheck: skipContainerCheck, - selectPrevious: true, - ); - - if (ref.mounted && previousTabId != null) { - return selectTab(previousTabId); - } - - return false; - } + }) => _selectAdjacentTab( + tabId, + containerId: containerId, + skipContainerCheck: skipContainerCheck, + selectPrevious: true, + ); Future selectNextTab( String tabId, { String? containerId, bool skipContainerCheck = true, + }) => _selectAdjacentTab( + tabId, + containerId: containerId, + skipContainerCheck: skipContainerCheck, + selectPrevious: false, + ); + + /// Moves the selection one step through the tab sequence. + /// + /// Unscoped calls — the tab bar swipe and the next/previous tab gestures — + /// step through the *rendered* order + /// ([sequentialTabNavigationOrderProvider]) so navigation matches the tabs the + /// user sees, including the tray's sort type, grouping, filters and + /// pinned-first handling. That order is authoritative once it exists, and + /// every outcome stays inside it: + /// + /// - current tab in the order: step one row, stopping at either end; + /// - current tab outside it — hidden by the active filter, or folded into a + /// collapsed group — enter the visible sequence from the end the step comes + /// from, rather than jumping to a tab the filter excludes; + /// - nothing visible at all: do nothing. + /// + /// The storage-order path is left for calls that scope navigation to a + /// container (which the rendered order, tied to the selected container, cannot + /// answer) and for the brief window before the tree data has loaded. + Future _selectAdjacentTab( + String tabId, { + required String? containerId, + required bool skipContainerCheck, + required bool selectPrevious, }) async { - final previousTabId = await _adjacentVisibleTabByOrder( + if (containerId == null && skipContainerCheck) { + final visibleOrder = ref.read(sequentialTabNavigationOrderProvider).value; + + if (visibleOrder != null) { + if (visibleOrder.isEmpty) { + return false; + } + + final index = visibleOrder.indexOf(tabId); + + if (index < 0) { + return selectTab( + selectPrevious ? visibleOrder.last : visibleOrder.first, + ); + } + + final targetIndex = selectPrevious ? index - 1 : index + 1; + + if (targetIndex < 0 || targetIndex >= visibleOrder.length) { + return false; + } + + return selectTab(visibleOrder[targetIndex]); + } + } + + final adjacentTabId = await _adjacentVisibleTabByOrder( tabId, containerId: containerId, skipContainerCheck: skipContainerCheck, - selectPrevious: false, + selectPrevious: selectPrevious, ); - if (ref.mounted && previousTabId != null) { - return selectTab(previousTabId); + if (ref.mounted && adjacentTabId != null) { + return selectTab(adjacentTabId); } return false; @@ -546,12 +595,14 @@ class TabRepository extends _$TabRepository { required bool skipContainerCheck, required bool selectPrevious, }) { - // "Previous/next" here is interpreted relative to the *tab bar* - // direction, even when the user triggered the navigation from the tab - // tray (which has its own `tabListDirection`). If the two settings - // disagree, "next tab" while looking at the tray flows by tab-bar - // direction. Treat as intentional — keyboard / gesture navigation is - // anchored to the bar's mental model. + // Storage-order walk: neighbours by `order_key` only, so it sees neither + // the tray's sort and filters nor its grouping. User-facing sequential + // navigation goes through the rendered order in [_selectAdjacentTab] and + // reaches this only as a fallback; what remains here is picking a tab + // after a close and container-scoped stepping. + // + // "Previous/next" is interpreted relative to the *tab bar* direction, + // which is the only direction this path has to go by. final newestFirst = ref.read(generalSettingsWithDefaultsProvider).tabBarDirection == TabDirection.newestFirst; @@ -946,6 +997,18 @@ class TabRepository extends _$TabRepository { @override void build() { + // Hold an active listener on the rendered navigation order: swipes and + // gestures read it synchronously, and Riverpod pauses a provider nothing is + // listening to — a one-off read would neither keep it current nor guarantee + // it has data when the first swipe arrives. Listened rather than watched + // because it changes with every tab update, which must not rebuild this + // repository; the callback is intentionally empty. + ref.listen( + sequentialTabNavigationOrderProvider, + (_, _) {}, + fireImmediately: true, + ); + final eventSerivce = ref.watch(eventServiceProvider); final tabContentService = ref.watch(tabContentServiceProvider); diff --git a/apps/weblibre/lib/features/geckoview/domain/repositories/tab.g.dart b/apps/weblibre/lib/features/geckoview/domain/repositories/tab.g.dart index 4bd00c28..99832299 100644 --- a/apps/weblibre/lib/features/geckoview/domain/repositories/tab.g.dart +++ b/apps/weblibre/lib/features/geckoview/domain/repositories/tab.g.dart @@ -41,7 +41,7 @@ final class TabRepositoryProvider } } -String _$tabRepositoryHash() => r'c94ccf85da3fee36ebac3521f51f265a5f8d34d3'; +String _$tabRepositoryHash() => r'797520166026d1f0272cf713aa1c25ecf129c236'; abstract class _$TabRepository extends $Notifier { void build(); diff --git a/apps/weblibre/lib/features/geckoview/features/browser/domain/providers.dart b/apps/weblibre/lib/features/geckoview/features/browser/domain/providers.dart index bbde117d..b9931000 100644 --- a/apps/weblibre/lib/features/geckoview/features/browser/domain/providers.dart +++ b/apps/weblibre/lib/features/geckoview/features/browser/domain/providers.dart @@ -1211,6 +1211,88 @@ EquatableValue> groupedTabListItems( return EquatableValue(result); } +/// The final row order the tab tray renders, i.e. +/// [groupedTabListItemsProvider] plus the flat-mode post-processing: with +/// hierarchy display turned off there are no groups to keep together, so +/// pinned tabs move ahead of unpinned ones across the whole list. +/// +/// Shared by the list view, the grid view and sequential tab navigation so all +/// three agree on what "the tab after this one" means. +@Riverpod() +EquatableValue> visibleTabListItems( + Ref ref, { + required String? containerId, +}) { + final groupedItems = ref + .watch(groupedTabListItemsProvider(containerId: containerId)) + .value; + + final filterOptions = ref.watch(tabViewFilterControllerProvider); + if (filterOptions.showHierarchicalTabs || !filterOptions.sortPinnedFirst) { + return EquatableValue(groupedItems); + } + + final pinnedTabIds = ref.watch( + watchPinnedTabIdsProvider.select( + (value) => value.value ?? const {}, + ), + ); + + return EquatableValue([ + ...groupedItems.where((item) => pinnedTabIds.contains(item.tabId)), + ...groupedItems.where((item) => !pinnedTabIds.contains(item.tabId)), + ]); +} + +/// Flat tab id order used by sequential tab navigation: the tab bar swipe +/// action and the next/previous tab gestures. +/// +/// Navigation follows the rendered tray order instead of the raw storage +/// `order_key`, so it carries the active sort type, tree grouping, collapsed +/// groups, pinned-first handling and the tab-type/date filter — stepping to the +/// tab the user sees next to the current one rather than to an unrelated +/// `order_key` neighbour. +/// +/// "Previous" is a step towards the top of that order and "next" a step +/// towards its end, so direction follows `tabListDirection` (baked into the +/// order) rather than `tabBarDirection`. The two only disagree when the user +/// sets them apart, and the tray order is the one the sequence is built from. +/// +/// The tray's own search results are deliberately not part of this: the swipe +/// and the gestures are only reachable with the tray closed. +/// +/// `null` means the underlying tree data has not arrived yet — the only state +/// in which the caller may fall back to storage order. An empty list is a real +/// answer ("the filter leaves nothing to move to") and must not be mistaken for +/// a missing one, or the filter the user set would be bypassed. +/// +/// Kept alive and actively listened to by [TabRepository]: it is consumed by a +/// synchronous `ref.read` at the moment of the swipe/gesture, from outside the +/// widget tree. Without a listener Riverpod pauses the chain when nothing is on +/// screen watching it, so the order could go stale — or be created empty on the +/// read, with its tree stream still loading, and silently drop navigation back +/// to storage order. It is alive anyway whenever the quick tab switcher or the +/// tray is on screen — both watch the same [groupedTabListItemsProvider] chain. +@Riverpod(keepAlive: true) +EquatableValue?> sequentialTabNavigationOrder(Ref ref) { + final containerId = ref.watch(selectedContainerProvider); + + final hasTreeData = ref.watch( + watchTabsWithRootAndDepthProvider( + containerId, + ).select((value) => value.hasValue), + ); + if (!hasTreeData) { + return EquatableValue(null); + } + + final visibleItems = ref + .watch(visibleTabListItemsProvider(containerId: containerId)) + .value; + + return EquatableValue([for (final item in visibleItems) item.tabId]); +} + String _nearestVisibleParentId( TabsWithRootAndDepthResult row, String rootId, diff --git a/apps/weblibre/lib/features/geckoview/features/browser/domain/providers.g.dart b/apps/weblibre/lib/features/geckoview/features/browser/domain/providers.g.dart index f5f9d49b..bf8a0156 100644 --- a/apps/weblibre/lib/features/geckoview/features/browser/domain/providers.g.dart +++ b/apps/weblibre/lib/features/geckoview/features/browser/domain/providers.g.dart @@ -1253,3 +1253,271 @@ final class GroupedTabListItemsFamily extends $Family @override String toString() => r'groupedTabListItemsProvider'; } + +/// The final row order the tab tray renders, i.e. +/// [groupedTabListItemsProvider] plus the flat-mode post-processing: with +/// hierarchy display turned off there are no groups to keep together, so +/// pinned tabs move ahead of unpinned ones across the whole list. +/// +/// Shared by the list view, the grid view and sequential tab navigation so all +/// three agree on what "the tab after this one" means. + +@ProviderFor(visibleTabListItems) +final visibleTabListItemsProvider = VisibleTabListItemsFamily._(); + +/// The final row order the tab tray renders, i.e. +/// [groupedTabListItemsProvider] plus the flat-mode post-processing: with +/// hierarchy display turned off there are no groups to keep together, so +/// pinned tabs move ahead of unpinned ones across the whole list. +/// +/// Shared by the list view, the grid view and sequential tab navigation so all +/// three agree on what "the tab after this one" means. + +final class VisibleTabListItemsProvider + extends + $FunctionalProvider< + EquatableValue>, + EquatableValue>, + EquatableValue> + > + with $Provider>> { + /// The final row order the tab tray renders, i.e. + /// [groupedTabListItemsProvider] plus the flat-mode post-processing: with + /// hierarchy display turned off there are no groups to keep together, so + /// pinned tabs move ahead of unpinned ones across the whole list. + /// + /// Shared by the list view, the grid view and sequential tab navigation so all + /// three agree on what "the tab after this one" means. + VisibleTabListItemsProvider._({ + required VisibleTabListItemsFamily super.from, + required String? super.argument, + }) : super( + retry: null, + name: r'visibleTabListItemsProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$visibleTabListItemsHash(); + + @override + String toString() { + return r'visibleTabListItemsProvider' + '' + '($argument)'; + } + + @$internal + @override + $ProviderElement>> $createElement( + $ProviderPointer pointer, + ) => $ProviderElement(pointer); + + @override + EquatableValue> create(Ref ref) { + final argument = this.argument as String?; + return visibleTabListItems(ref, containerId: argument); + } + + /// {@macro riverpod.override_with_value} + Override overrideWithValue(EquatableValue> value) { + return $ProviderOverride( + origin: this, + providerOverride: + $SyncValueProvider>>(value), + ); + } + + @override + bool operator ==(Object other) { + return other is VisibleTabListItemsProvider && other.argument == argument; + } + + @override + int get hashCode { + return argument.hashCode; + } +} + +String _$visibleTabListItemsHash() => + r'5249a3e7e1f0b24e408987956856926d0958b0db'; + +/// The final row order the tab tray renders, i.e. +/// [groupedTabListItemsProvider] plus the flat-mode post-processing: with +/// hierarchy display turned off there are no groups to keep together, so +/// pinned tabs move ahead of unpinned ones across the whole list. +/// +/// Shared by the list view, the grid view and sequential tab navigation so all +/// three agree on what "the tab after this one" means. + +final class VisibleTabListItemsFamily extends $Family + with + $FunctionalFamilyOverride< + EquatableValue>, + String? + > { + VisibleTabListItemsFamily._() + : super( + retry: null, + name: r'visibleTabListItemsProvider', + dependencies: null, + $allTransitiveDependencies: null, + isAutoDispose: true, + ); + + /// The final row order the tab tray renders, i.e. + /// [groupedTabListItemsProvider] plus the flat-mode post-processing: with + /// hierarchy display turned off there are no groups to keep together, so + /// pinned tabs move ahead of unpinned ones across the whole list. + /// + /// Shared by the list view, the grid view and sequential tab navigation so all + /// three agree on what "the tab after this one" means. + + VisibleTabListItemsProvider call({required String? containerId}) => + VisibleTabListItemsProvider._(argument: containerId, from: this); + + @override + String toString() => r'visibleTabListItemsProvider'; +} + +/// Flat tab id order used by sequential tab navigation: the tab bar swipe +/// action and the next/previous tab gestures. +/// +/// Navigation follows the rendered tray order instead of the raw storage +/// `order_key`, so it carries the active sort type, tree grouping, collapsed +/// groups, pinned-first handling and the tab-type/date filter — stepping to the +/// tab the user sees next to the current one rather than to an unrelated +/// `order_key` neighbour. +/// +/// "Previous" is a step towards the top of that order and "next" a step +/// towards its end, so direction follows `tabListDirection` (baked into the +/// order) rather than `tabBarDirection`. The two only disagree when the user +/// sets them apart, and the tray order is the one the sequence is built from. +/// +/// The tray's own search results are deliberately not part of this: the swipe +/// and the gestures are only reachable with the tray closed. +/// +/// `null` means the underlying tree data has not arrived yet — the only state +/// in which the caller may fall back to storage order. An empty list is a real +/// answer ("the filter leaves nothing to move to") and must not be mistaken for +/// a missing one, or the filter the user set would be bypassed. +/// +/// Kept alive and actively listened to by [TabRepository]: it is consumed by a +/// synchronous `ref.read` at the moment of the swipe/gesture, from outside the +/// widget tree. Without a listener Riverpod pauses the chain when nothing is on +/// screen watching it, so the order could go stale — or be created empty on the +/// read, with its tree stream still loading, and silently drop navigation back +/// to storage order. It is alive anyway whenever the quick tab switcher or the +/// tray is on screen — both watch the same [groupedTabListItemsProvider] chain. + +@ProviderFor(sequentialTabNavigationOrder) +final sequentialTabNavigationOrderProvider = + SequentialTabNavigationOrderProvider._(); + +/// Flat tab id order used by sequential tab navigation: the tab bar swipe +/// action and the next/previous tab gestures. +/// +/// Navigation follows the rendered tray order instead of the raw storage +/// `order_key`, so it carries the active sort type, tree grouping, collapsed +/// groups, pinned-first handling and the tab-type/date filter — stepping to the +/// tab the user sees next to the current one rather than to an unrelated +/// `order_key` neighbour. +/// +/// "Previous" is a step towards the top of that order and "next" a step +/// towards its end, so direction follows `tabListDirection` (baked into the +/// order) rather than `tabBarDirection`. The two only disagree when the user +/// sets them apart, and the tray order is the one the sequence is built from. +/// +/// The tray's own search results are deliberately not part of this: the swipe +/// and the gestures are only reachable with the tray closed. +/// +/// `null` means the underlying tree data has not arrived yet — the only state +/// in which the caller may fall back to storage order. An empty list is a real +/// answer ("the filter leaves nothing to move to") and must not be mistaken for +/// a missing one, or the filter the user set would be bypassed. +/// +/// Kept alive and actively listened to by [TabRepository]: it is consumed by a +/// synchronous `ref.read` at the moment of the swipe/gesture, from outside the +/// widget tree. Without a listener Riverpod pauses the chain when nothing is on +/// screen watching it, so the order could go stale — or be created empty on the +/// read, with its tree stream still loading, and silently drop navigation back +/// to storage order. It is alive anyway whenever the quick tab switcher or the +/// tray is on screen — both watch the same [groupedTabListItemsProvider] chain. + +final class SequentialTabNavigationOrderProvider + extends + $FunctionalProvider< + EquatableValue?>, + EquatableValue?>, + EquatableValue?> + > + with $Provider?>> { + /// Flat tab id order used by sequential tab navigation: the tab bar swipe + /// action and the next/previous tab gestures. + /// + /// Navigation follows the rendered tray order instead of the raw storage + /// `order_key`, so it carries the active sort type, tree grouping, collapsed + /// groups, pinned-first handling and the tab-type/date filter — stepping to the + /// tab the user sees next to the current one rather than to an unrelated + /// `order_key` neighbour. + /// + /// "Previous" is a step towards the top of that order and "next" a step + /// towards its end, so direction follows `tabListDirection` (baked into the + /// order) rather than `tabBarDirection`. The two only disagree when the user + /// sets them apart, and the tray order is the one the sequence is built from. + /// + /// The tray's own search results are deliberately not part of this: the swipe + /// and the gestures are only reachable with the tray closed. + /// + /// `null` means the underlying tree data has not arrived yet — the only state + /// in which the caller may fall back to storage order. An empty list is a real + /// answer ("the filter leaves nothing to move to") and must not be mistaken for + /// a missing one, or the filter the user set would be bypassed. + /// + /// Kept alive and actively listened to by [TabRepository]: it is consumed by a + /// synchronous `ref.read` at the moment of the swipe/gesture, from outside the + /// widget tree. Without a listener Riverpod pauses the chain when nothing is on + /// screen watching it, so the order could go stale — or be created empty on the + /// read, with its tree stream still loading, and silently drop navigation back + /// to storage order. It is alive anyway whenever the quick tab switcher or the + /// tray is on screen — both watch the same [groupedTabListItemsProvider] chain. + SequentialTabNavigationOrderProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'sequentialTabNavigationOrderProvider', + isAutoDispose: false, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$sequentialTabNavigationOrderHash(); + + @$internal + @override + $ProviderElement?>> $createElement( + $ProviderPointer pointer, + ) => $ProviderElement(pointer); + + @override + EquatableValue?> create(Ref ref) { + return sequentialTabNavigationOrder(ref); + } + + /// {@macro riverpod.override_with_value} + Override overrideWithValue(EquatableValue?> value) { + return $ProviderOverride( + origin: this, + providerOverride: $SyncValueProvider?>>( + value, + ), + ); + } +} + +String _$sequentialTabNavigationOrderHash() => + r'2a6b1965657942524e8e514cd4deb0ce77667fe1'; diff --git a/apps/weblibre/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/bottom_app_bar.dart b/apps/weblibre/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/bottom_app_bar.dart index cdf9a8e0..e2f80f8b 100644 --- a/apps/weblibre/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/bottom_app_bar.dart +++ b/apps/weblibre/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/bottom_app_bar.dart @@ -380,7 +380,10 @@ class BrowserTabBar extends HookConsumerWidget { final dragStartPosition = useRef(Offset.zero); // Swipe along the primary switch axis moves between tabs. [delta] is - // (dragStart - dragEnd) along that axis; its sign chooses prev/next. + // (dragStart - dragEnd) along that axis, so a right-to-left (or upward) + // swipe is positive and moves *up* the visible tab order, a rightward (or + // downward) swipe moves down it — the swipe drags the list under the + // finger. Future switchTabsBy(double delta) async { final selectedTab = ref.read(selectedTabProvider); final setting = await ref @@ -395,7 +398,7 @@ class BrowserTabBar extends HookConsumerWidget { .read(tabRepositoryProvider.notifier) .selectPreviouslyOpenedTab(selectedTab); case TabBarSwipeAction.navigateOrderedTabs: - if (delta < 0) { + if (delta > 0) { await ref .read(tabRepositoryProvider.notifier) .selectPreviousTab(selectedTab); diff --git a/apps/weblibre/lib/features/geckoview/features/browser/presentation/widgets/tab_view/tab_grid_view.dart b/apps/weblibre/lib/features/geckoview/features/browser/presentation/widgets/tab_view/tab_grid_view.dart index 0bfe9080..e656be65 100644 --- a/apps/weblibre/lib/features/geckoview/features/browser/presentation/widgets/tab_view/tab_grid_view.dart +++ b/apps/weblibre/lib/features/geckoview/features/browser/presentation/widgets/tab_view/tab_grid_view.dart @@ -222,11 +222,11 @@ class _TabGridView extends HookConsumerWidget { ), ]; } else { - final grouped = ref.watch( - groupedTabListItemsProvider(containerId: containerId), + final visibleItems = ref.watch( + visibleTabListItemsProvider(containerId: containerId), ); primaryRows = [ - for (final item in grouped.value) + for (final item in visibleItems.value) switch (item) { TabListStandaloneItem(:final tabId) => TabViewItem.standalone( tabId: tabId, @@ -241,15 +241,6 @@ class _TabGridView extends HookConsumerWidget { : TabViewItem.standalone(tabId: c.tabId), }, ]; - if (!showHierarchicalTabs && filterOptions.sortPinnedFirst) { - final pinned = primaryRows - .where((r) => pinnedTabIds.contains(r.tabId)) - .toList(); - final unpinned = primaryRows - .where((r) => !pinnedTabIds.contains(r.tabId)) - .toList(); - primaryRows = [...pinned, ...unpinned]; - } } final tabSuggestionsEnabled = ref.watch( diff --git a/apps/weblibre/lib/features/geckoview/features/browser/presentation/widgets/tab_view/tab_list_view.dart b/apps/weblibre/lib/features/geckoview/features/browser/presentation/widgets/tab_view/tab_list_view.dart index ab00a5f2..d119fb1b 100644 --- a/apps/weblibre/lib/features/geckoview/features/browser/presentation/widgets/tab_view/tab_list_view.dart +++ b/apps/weblibre/lib/features/geckoview/features/browser/presentation/widgets/tab_view/tab_list_view.dart @@ -283,11 +283,11 @@ class _TabListView extends HookConsumerWidget { ), ]; } else { - final grouped = ref.watch( - groupedTabListItemsProvider(containerId: containerId), + final visibleItems = ref.watch( + visibleTabListItemsProvider(containerId: containerId), ); primaryRows = [ - for (final item in grouped.value) + for (final item in visibleItems.value) switch (item) { TabListStandaloneItem(:final tabId) => TabViewItem.standalone( tabId: tabId, @@ -302,15 +302,6 @@ class _TabListView extends HookConsumerWidget { : TabViewItem.standalone(tabId: c.tabId), }, ]; - if (!showHierarchicalTabs && filterOptions.sortPinnedFirst) { - final pinned = primaryRows - .where((r) => pinnedTabIds.contains(r.tabId)) - .toList(); - final unpinned = primaryRows - .where((r) => !pinnedTabIds.contains(r.tabId)) - .toList(); - primaryRows = [...pinned, ...unpinned]; - } } final tabSuggestionsEnabled = ref.watch(