From 0118351e2498578e538e851ec5d6ccd4bf4d3c73 Mon Sep 17 00:00:00 2001 From: Illia Panasenko Date: Tue, 11 Aug 2026 23:28:13 +0200 Subject: [PATCH 1/2] fix(web): hide archive confirmation with new sidebar --- .../components/settings/SettingsPanels.tsx | 48 ++++++++++--------- .../settings/SettingsSidebarNav.tsx | 8 +++- .../settings/settingsSearch.test.ts | 10 ++++ .../src/components/settings/settingsSearch.ts | 10 ++++ 4 files changed, 52 insertions(+), 24 deletions(-) diff --git a/apps/web/src/components/settings/SettingsPanels.tsx b/apps/web/src/components/settings/SettingsPanels.tsx index e4cfbe9ac033..45b76c21b85f 100644 --- a/apps/web/src/components/settings/SettingsPanels.tsx +++ b/apps/web/src/components/settings/SettingsPanels.tsx @@ -2150,31 +2150,33 @@ export function GeneralSettingsPanel() { } /> - - updateSettings({ - confirmThreadArchive: DEFAULT_UNIFIED_SETTINGS.confirmThreadArchive, - }) + {settings.legacySidebarEnabled ? ( + + updateSettings({ + confirmThreadArchive: DEFAULT_UNIFIED_SETTINGS.confirmThreadArchive, + }) + } + /> + ) : null + } + control={ + + updateSettings({ confirmThreadArchive: Boolean(checked) }) } + aria-label="Confirm thread archiving" /> - ) : null - } - control={ - - updateSettings({ confirmThreadArchive: Boolean(checked) }) - } - aria-label="Confirm thread archiving" - /> - } - /> + } + /> + ) : null} (null); const [query, setQuery] = useState(""); const [activeResultIndex, setActiveResultIndex] = useState(0); - const results = useMemo(() => searchSettings(query), [query]); + const legacySidebarEnabled = useLegacySidebarEnabled(); + const results = useMemo( + () => searchSettings(query, visibleSettingsSearchItems(legacySidebarEnabled)), + [legacySidebarEnabled, query], + ); const isSearching = query.trim().length > 0; const hasResults = results.length > 0; diff --git a/apps/web/src/components/settings/settingsSearch.test.ts b/apps/web/src/components/settings/settingsSearch.test.ts index a5851b2c714e..3ba15d3c0599 100644 --- a/apps/web/src/components/settings/settingsSearch.test.ts +++ b/apps/web/src/components/settings/settingsSearch.test.ts @@ -5,6 +5,7 @@ import { searchSettings, SETTINGS_SEARCH_ITEMS, type SettingsSearchItem, + visibleSettingsSearchItems, } from "./settingsSearch"; const ITEMS: ReadonlyArray = [ @@ -65,6 +66,15 @@ describe("searchSettings", () => { expect(new Set(ids).size).toBe(ids.length); }); + it("only exposes archive confirmation for the legacy sidebar", () => { + expect( + visibleSettingsSearchItems(false).some((item) => item.id === "archive-confirmation"), + ).toBe(false); + expect( + visibleSettingsSearchItems(true).some((item) => item.id === "archive-confirmation"), + ).toBe(true); + }); + it("serves anchor props to panels from the catalog", () => { expect(searchableSetting("word-wrap")).toEqual({ id: "word-wrap", title: "Word wrap" }); expect(searchableSetting("archive")).toEqual({ id: "archive", title: "Archived threads" }); diff --git a/apps/web/src/components/settings/settingsSearch.ts b/apps/web/src/components/settings/settingsSearch.ts index 34fd4602f784..5e0cb2ff06a1 100644 --- a/apps/web/src/components/settings/settingsSearch.ts +++ b/apps/web/src/components/settings/settingsSearch.ts @@ -12,6 +12,7 @@ export interface SettingsSearchItem { readonly title: string; readonly to: SettingsPath; readonly targetId?: string; + readonly requiresLegacySidebar?: boolean; } /** @@ -138,6 +139,7 @@ export const SETTINGS_SEARCH_ITEMS = [ id: "archive-confirmation", title: "Archive confirmation", to: "/settings/general", + requiresLegacySidebar: true, }, { id: "delete-confirmation", @@ -233,3 +235,11 @@ export function searchSettings( return items.filter((item) => normalizeSearchText(item.title).includes(normalizedQuery)); } + +export function visibleSettingsSearchItems( + legacySidebarEnabled: boolean, + items: ReadonlyArray = SETTINGS_SEARCH_ITEMS, +): ReadonlyArray { + if (legacySidebarEnabled) return items; + return items.filter((item) => item.requiresLegacySidebar !== true); +} From ab048686d60c78040f5a9df4694f2e04334b2004 Mon Sep 17 00:00:00 2001 From: Illia Panasenko Date: Tue, 11 Aug 2026 23:43:57 +0200 Subject: [PATCH 2/2] fix(web): clamp filtered settings search selection --- .../settings/SettingsSidebarNav.tsx | 25 +++++++++++-------- .../settings/settingsSearch.test.ts | 7 ++++++ .../src/components/settings/settingsSearch.ts | 4 +++ 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/apps/web/src/components/settings/SettingsSidebarNav.tsx b/apps/web/src/components/settings/SettingsSidebarNav.tsx index 2b0114c4aaf6..f6cb138ccd34 100644 --- a/apps/web/src/components/settings/SettingsSidebarNav.tsx +++ b/apps/web/src/components/settings/SettingsSidebarNav.tsx @@ -37,6 +37,7 @@ import { T3ConnectSidebarAvatar, T3ConnectSidebarSignIn } from "../clerk/T3Conne import { useLegacySidebarEnabled } from "../../hooks/useSettings"; import { scrollToSettingsTarget } from "./settingsLayout"; import { + clampSettingsSearchResultIndex, searchSettings, SETTINGS_SECTION_LABELS, type SettingsPath, @@ -84,16 +85,20 @@ export function SettingsSidebarNav({ pathname }: { pathname: string }) { () => searchSettings(query, visibleSettingsSearchItems(legacySidebarEnabled)), [legacySidebarEnabled, query], ); + const resolvedActiveResultIndex = clampSettingsSearchResultIndex( + activeResultIndex, + results.length, + ); const isSearching = query.trim().length > 0; const hasResults = results.length > 0; useEffect(() => { - const result = results[activeResultIndex]; + const result = results[resolvedActiveResultIndex]; if (!result) return; document .getElementById(`settings-search-result-${result.id}`) ?.scrollIntoView({ block: "nearest" }); - }, [activeResultIndex, results]); + }, [resolvedActiveResultIndex, results]); useEffect(() => { const handleKeyDown = (event: globalThis.KeyboardEvent) => { @@ -166,21 +171,21 @@ export function SettingsSidebarNav({ pathname }: { pathname: string }) { if (results.length === 0) return; if (event.key === "ArrowDown") { event.preventDefault(); - setActiveResultIndex((index) => (index + 1) % results.length); + setActiveResultIndex((resolvedActiveResultIndex + 1) % results.length); return; } if (event.key === "ArrowUp") { event.preventDefault(); - setActiveResultIndex((index) => (index - 1 + results.length) % results.length); + setActiveResultIndex((resolvedActiveResultIndex - 1 + results.length) % results.length); return; } if (event.key === "Enter") { event.preventDefault(); - const result = results[activeResultIndex]; + const result = results[resolvedActiveResultIndex]; if (result) handleSearchResultClick(result); } }, - [activeResultIndex, clearSearch, handleSearchResultClick, isSearching, results], + [clearSearch, handleSearchResultClick, isSearching, resolvedActiveResultIndex, results], ); const handleBackClick = useCallback(() => { if (isMobile) { @@ -217,8 +222,8 @@ export function SettingsSidebarNav({ pathname }: { pathname: string }) { aria-expanded={isSearching && hasResults} aria-controls={isSearching && hasResults ? "settings-search-results" : undefined} aria-activedescendant={ - isSearching && results[activeResultIndex] - ? `settings-search-result-${results[activeResultIndex].id}` + isSearching && results[resolvedActiveResultIndex] + ? `settings-search-result-${results[resolvedActiveResultIndex].id}` : undefined } className="min-w-0 flex-1 [&_[data-slot=input]]:h-auto [&_[data-slot=input]]:p-0 [&_[data-slot=input]]:leading-normal [&_[data-slot=input]]:text-sm [&_[data-slot=input]]:font-medium [&_[data-slot=input]]:text-sidebar-foreground [&_[data-slot=input]]:placeholder:text-sidebar-muted-foreground" @@ -261,10 +266,10 @@ export function SettingsSidebarNav({ pathname }: { pathname: string }) { setActiveResultIndex(index)} onClick={() => handleSearchResultClick(item)} diff --git a/apps/web/src/components/settings/settingsSearch.test.ts b/apps/web/src/components/settings/settingsSearch.test.ts index 3ba15d3c0599..6a3d92717c9a 100644 --- a/apps/web/src/components/settings/settingsSearch.test.ts +++ b/apps/web/src/components/settings/settingsSearch.test.ts @@ -1,6 +1,7 @@ import { describe, expect, it } from "vite-plus/test"; import { + clampSettingsSearchResultIndex, searchableSetting, searchSettings, SETTINGS_SEARCH_ITEMS, @@ -75,6 +76,12 @@ describe("searchSettings", () => { ).toBe(true); }); + it("clamps the active result after visible settings shrink", () => { + expect(clampSettingsSearchResultIndex(1, 1)).toBe(0); + expect(clampSettingsSearchResultIndex(1, 2)).toBe(1); + expect(clampSettingsSearchResultIndex(0, 0)).toBe(0); + }); + it("serves anchor props to panels from the catalog", () => { expect(searchableSetting("word-wrap")).toEqual({ id: "word-wrap", title: "Word wrap" }); expect(searchableSetting("archive")).toEqual({ id: "archive", title: "Archived threads" }); diff --git a/apps/web/src/components/settings/settingsSearch.ts b/apps/web/src/components/settings/settingsSearch.ts index 5e0cb2ff06a1..00536423a6b4 100644 --- a/apps/web/src/components/settings/settingsSearch.ts +++ b/apps/web/src/components/settings/settingsSearch.ts @@ -226,6 +226,10 @@ function normalizeSearchText(value: string): string { .trim(); } +export function clampSettingsSearchResultIndex(index: number, resultCount: number): number { + return Math.max(0, Math.min(index, resultCount - 1)); +} + export function searchSettings( query: string, items: ReadonlyArray = SETTINGS_SEARCH_ITEMS,