fix(app): keep quick switcher focus when results are recomputed (#1746)

This commit is contained in:
Hampus authored and GitHub committed 2026-08-19 12:55:04 +02:00
1 parent 30a61ce90f
commit 7b5c82c6cf
3 files changed
+64 -1

No files matched your search

@@ -19,6 +19,7 @@ import {
generateQueryModeResults,
resolveTransformedMember,
} from '@app/features/search/state/QuickSwitcherResultGenerators';
import {resolveRecomputedSelectedIndex} from '@app/features/search/state/QuickSwitcherSelection';
import type {
CandidateSets,
ComputeResultsForQueryResult,
@@ -404,10 +405,11 @@ class QuickSwitcher {
if (options.invalidateCandidates ?? true) {
this.invalidateCandidateSets();
}
const previous = this.results[this.selectedIndex];
const {queryMode, results, selectedIndex} = this.computeResultsForQuery(this.query);
this.queryMode = queryMode;
this.results = results;
this.selectedIndex = selectedIndex;
this.selectedIndex = resolveRecomputedSelectedIndex(previous, results, selectedIndex);
}
private computeResultsForQuery(query: string): ComputeResultsForQueryResult {
@@ -0,0 +1,46 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {resolveRecomputedSelectedIndex} from '@app/features/search/state/QuickSwitcherSelection';
import type {QuickSwitcherResult} from '@app/features/search/state/QuickSwitcherTypes';
import {QuickSwitcherResultTypes} from '@fluxer/constants/src/QuickSwitcherConstants';
import {describe, expect, it} from 'vitest';
const result = (type: QuickSwitcherResult['type'], id: string) => ({type, id}) as QuickSwitcherResult;
describe('resolveRecomputedSelectedIndex', () => {
it('follows the focused result when the list is reordered', () => {
const previous = result(QuickSwitcherResultTypes.TEXT_CHANNEL, 'c3');
const results = [
result(QuickSwitcherResultTypes.TEXT_CHANNEL, 'c3'),
result(QuickSwitcherResultTypes.TEXT_CHANNEL, 'c1'),
result(QuickSwitcherResultTypes.TEXT_CHANNEL, 'c2'),
];
expect(resolveRecomputedSelectedIndex(previous, results, 0)).toBe(0);
});
it('keeps the focused result when entries are inserted above it', () => {
const previous = result(QuickSwitcherResultTypes.TEXT_CHANNEL, 'c2');
const results = [
result(QuickSwitcherResultTypes.HEADER, 'h1'),
result(QuickSwitcherResultTypes.TEXT_CHANNEL, 'c9'),
result(QuickSwitcherResultTypes.TEXT_CHANNEL, 'c2'),
];
expect(resolveRecomputedSelectedIndex(previous, results, 1)).toBe(2);
});
it('falls back when the focused result disappears', () => {
const previous = result(QuickSwitcherResultTypes.TEXT_CHANNEL, 'gone');
const results = [result(QuickSwitcherResultTypes.TEXT_CHANNEL, 'c1')];
expect(resolveRecomputedSelectedIndex(previous, results, 0)).toBe(0);
});
it('does not confuse ids that repeat across result types', () => {
const previous = result(QuickSwitcherResultTypes.GUILD, '42');
const results = [result(QuickSwitcherResultTypes.TEXT_CHANNEL, '42'), result(QuickSwitcherResultTypes.GUILD, '42')];
expect(resolveRecomputedSelectedIndex(previous, results, 0)).toBe(1);
});
it('uses the fallback when nothing was focused', () => {
expect(resolveRecomputedSelectedIndex(undefined, [result(QuickSwitcherResultTypes.GUILD, 'g')], 0)).toBe(0);
});
});
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import type {QuickSwitcherResult} from '@app/features/search/state/QuickSwitcherTypes';
export function resolveRecomputedSelectedIndex(
previous: QuickSwitcherResult | undefined,
results: ReadonlyArray<QuickSwitcherResult>,
fallbackIndex: number,
): number {
if (!previous) {
return fallbackIndex;
}
const restored = results.findIndex((result) => result.type === previous.type && result.id === previous.id);
return restored >= 0 ? restored : fallbackIndex;
}