From 9de3249e9c95ddcd0196cdfa855d1f817f790a66 Mon Sep 17 00:00:00 2001 From: Marco Beretta <81851188+berry-13@users.noreply.github.com> Date: Thu, 18 Jun 2026 14:51:07 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=9B=EF=B8=8F=20feat:=20Redesign=20Sett?= =?UTF-8?q?ings=20with=20Registry-Driven=20Dialog,=20Search,=20and=20Mobil?= =?UTF-8?q?e=20Drill-In=20(#13722)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * i18n: add settings reorganization keys * feat(settings): add tab/section types and tab metadata * feat(settings): add useSettingsContext guard hook * feat(settings): add pure settings search filter with tests * feat(settings): extract selectors and add control wrappers * feat(settings): add setting registry, memory and billing controls, integrity test * feat(settings): add Section and Advanced disclosure with test * feat(settings): add content pane with tab and search views * feat(settings): add sidebar and dialog shell with tests * refactor(settings): wire new dialog and remove superseded containers * fix(settings): restore speech external engine option, escape-to-clear search, results a11y - SpeechControls.tsx: read sttExternal/ttsExternal from useGetCustomConfigSpeechQuery instead of hardcoding false, so external engine options appear on qualifying deployments - Sidebar: Escape clears search input when non-empty, stops propagation to avoid closing dialog - Content: persistent aria-live="polite" wrapper covers both populated results and empty state - context: useMemo on returned ctx object so Content's useMemo deps are referentially stable - locales/README.md: update stale path from deleted General.tsx to Selectors.tsx * refactor(settings): reorganize categories, remove advanced disclosure, add About - Re-categorize settings into logical groups (username display -> Chat/Messages, keep-screen-awake -> Accessibility, fork/prompts surfaced into Chat sections) - Dissolve thin Personalization tab; move Memory into Data & Privacy - Remove the Advanced collapsible; all settings always visible, destructive actions grouped in an always-visible Danger zone - Wire the new About tab into the registry-driven dialog - Standardize spacing with bordered, evenly-divided section cards - Use semantic text-text-* / border tokens so dark mode renders correctly - Sync LangSelector language-loading indicator from dev * feat(settings): move archived chats to the account menu Add an Archived chats item to the account dropdown next to My Files, opening the archived chats table in a modal. Removes it from the settings dialog where it no longer fit the data/privacy grouping. * feat(settings): polish About panel and use shared CopyButton - Flatten the build-info into a single divided key/value list (drop the redundant inner card now that it sits inside a section card) - Replace the hand-rolled copy button with the shared animated CopyButton - Shorten the copied label so it fits the button without clipping * fix(settings): set primary text color on setting rows for dark mode Leaf control labels rendered without a text color and fell back to the browser default (black), making them invisible on the dark panel. Set text-text-primary on the section and search-results row containers so labels inherit a visible color, matching the old container behavior. * fix(settings): use visible icon for dialog close button The plain multiplication-sign close button had no text color and was invisible on the dark panel. Replace it with the lucide X icon using text-text-secondary/hover:text-text-primary so it shows in both themes. * fix(nav): drop focus ring on account menu items, use hover background only The account-settings popover drew a 2px ring around the active menu item. Remove that override so items show only the standard hover background, consistent with every other menu. * fix(settings): replace native search clear with a real X button The settings search used type=search, whose native WebKit clear control rendered as a blue X. Switch to a text input and add a real lucide X clear button styled text-text-secondary, shown only when there's a query. * fix(speech): disable dependent dropdowns and switches when STT/TTS is off Add a disabled prop to the shared Dropdown component, then gate the speech engine/voice/language dropdowns and the automatic-playback switch on their parent toggle (speechToText / textToSpeech), matching the controls that already disabled correctly. * feat(settings): mobile drill-in navigation for settings tabs On small screens the horizontal scrolling tab row is replaced with a full-width vertical list (with chevrons); tapping a tab drills into its content with a Back header. Searching shows results full-width. Desktop keeps the side-by-side sidebar + content layout unchanged. * chore(settings): remove orphaned i18n keys, fix import order and review notes - Drop the i18n keys left unused after the refactor (old Commands/Balance/ Personalization tab labels, the Speech simple/advanced labels, and the former About section headings) - Sort imports in the rebased files the lint-staged hook never touched - Guard the language fallback against an empty navigator.languages - Import the RefObject type instead of leaning on the React namespace * feat(settings): searchable language dropdown Add an opt-in searchable mode to the shared Dropdown (Ariakit Select + Combobox) and use it for the language selector, which has 40+ options. The trigger styling is unchanged so it stays consistent with the other settings rows; only the popover gains a filter input. Accessibility: the filtered listbox is labeled, the empty state is moved out of the listbox and announced via an aria-live status region, and the decorative selected-state checkmark is hidden from assistive tech. * fix(settings): restore guards dropped in dialog refactor - Fall back to the General tab when the active tab becomes hidden (e.g. About when buildInfo is disabled) instead of rendering an empty panel. - Normalize a deprecated/invalid engineTTS (e.g. 'edge') back to browser during speech init so read-aloud controls keep rendering. - Hide the cloud browser voices toggle unless Browser TTS is active. * test(e2e): match agent-creation toast exactly to avoid SR-announce collision The agent builder spec asserted the creation toast with a non-exact getByText, which also matched Radix Toast's transient role="status" announce region ("Notification Successfully created ..."), causing a strict-mode violation. Mirror the mcp spec by using { exact: true }. * fix(settings): render the active panel as a tabpanel Wrap the non-search settings body in Tabs.Content so the selected panel gets role=tabpanel with Radix's id/aria-labelledby wiring, resolving the aria-controls target on each tab trigger. Search results stay a labeled live region (the tab list is hidden during mobile search, so a tabpanel aria-labelledby would dangle). --- client/src/components/Audio/Voices.tsx | 6 +- client/src/components/Nav/AccountSettings.tsx | 15 +- client/src/components/Nav/Settings.spec.tsx | 85 --- client/src/components/Nav/Settings.tsx | 286 +-------- .../Nav/Settings/BillingControls.tsx | 61 ++ .../src/components/Nav/Settings/Content.tsx | 81 +++ client/src/components/Nav/Settings/Dialog.tsx | 138 +++++ .../components/Nav/Settings/MemoryToggle.tsx | 49 ++ .../src/components/Nav/Settings/Section.tsx | 31 + .../src/components/Nav/Settings/Sidebar.tsx | 92 +++ .../Nav/Settings/SpeechControls.tsx | 13 + .../Nav/Settings/__tests__/Dialog.spec.tsx | 38 ++ .../Nav/Settings/__tests__/Sidebar.spec.tsx | 80 +++ .../Nav/Settings/__tests__/registry.spec.ts | 33 + .../Nav/Settings/__tests__/search.spec.ts | 30 + .../src/components/Nav/Settings/context.tsx | 66 ++ .../src/components/Nav/Settings/controls.tsx | 50 ++ client/src/components/Nav/Settings/index.ts | 1 + .../src/components/Nav/Settings/registry.tsx | 562 ++++++++++++++++++ client/src/components/Nav/Settings/search.ts | 43 ++ client/src/components/Nav/Settings/types.ts | 135 +++++ .../Nav/SettingsTabs/About/About.tsx | 92 ++- .../Nav/SettingsTabs/Account/Account.spec.tsx | 64 -- .../Nav/SettingsTabs/Account/Account.tsx | 43 -- .../Nav/SettingsTabs/Balance/Balance.tsx | 68 --- .../components/Nav/SettingsTabs/Chat/Chat.tsx | 131 ---- .../Nav/SettingsTabs/Commands/Commands.tsx | 82 --- .../components/Nav/SettingsTabs/Data/Data.tsx | 47 -- .../SettingsTabs/General/ArchivedChats.tsx | 28 - .../General/ArchivedChatsModal.tsx | 30 + .../General/LangSelector.spec.tsx | 2 +- .../General/{General.tsx => Selectors.tsx} | 96 +-- .../General/ThemeSelector.spec.tsx | 3 +- .../Nav/SettingsTabs/Personalization.tsx | 90 --- .../Speech/STT/EngineSTTDropdown.tsx | 4 +- .../Speech/STT/LanguageSTTDropdown.tsx | 4 +- .../Nav/SettingsTabs/Speech/Speech.tsx | 243 -------- .../Speech/TTS/AutomaticPlaybackSwitch.tsx | 3 + .../Speech/TTS/EngineTTSDropdown.tsx | 4 +- .../SettingsTabs/Speech/TTS/VoiceDropdown.tsx | 3 +- .../src/components/Nav/SettingsTabs/index.ts | 9 +- client/src/components/Share/ShareView.tsx | 2 +- .../src/hooks/Config/useSpeechSettingsInit.ts | 14 +- client/src/locales/README.md | 2 +- client/src/locales/en/translation.json | 53 +- client/src/style.css | 4 - e2e/specs/mock/agents.spec.ts | 4 +- packages/client/src/components/Dropdown.tsx | 180 ++++-- 48 files changed, 1808 insertions(+), 1392 deletions(-) delete mode 100644 client/src/components/Nav/Settings.spec.tsx create mode 100644 client/src/components/Nav/Settings/BillingControls.tsx create mode 100644 client/src/components/Nav/Settings/Content.tsx create mode 100644 client/src/components/Nav/Settings/Dialog.tsx create mode 100644 client/src/components/Nav/Settings/MemoryToggle.tsx create mode 100644 client/src/components/Nav/Settings/Section.tsx create mode 100644 client/src/components/Nav/Settings/Sidebar.tsx create mode 100644 client/src/components/Nav/Settings/SpeechControls.tsx create mode 100644 client/src/components/Nav/Settings/__tests__/Dialog.spec.tsx create mode 100644 client/src/components/Nav/Settings/__tests__/Sidebar.spec.tsx create mode 100644 client/src/components/Nav/Settings/__tests__/registry.spec.ts create mode 100644 client/src/components/Nav/Settings/__tests__/search.spec.ts create mode 100644 client/src/components/Nav/Settings/context.tsx create mode 100644 client/src/components/Nav/Settings/controls.tsx create mode 100644 client/src/components/Nav/Settings/index.ts create mode 100644 client/src/components/Nav/Settings/registry.tsx create mode 100644 client/src/components/Nav/Settings/search.ts create mode 100644 client/src/components/Nav/Settings/types.ts delete mode 100644 client/src/components/Nav/SettingsTabs/Account/Account.spec.tsx delete mode 100644 client/src/components/Nav/SettingsTabs/Account/Account.tsx delete mode 100644 client/src/components/Nav/SettingsTabs/Balance/Balance.tsx delete mode 100644 client/src/components/Nav/SettingsTabs/Chat/Chat.tsx delete mode 100644 client/src/components/Nav/SettingsTabs/Commands/Commands.tsx delete mode 100644 client/src/components/Nav/SettingsTabs/Data/Data.tsx delete mode 100644 client/src/components/Nav/SettingsTabs/General/ArchivedChats.tsx create mode 100644 client/src/components/Nav/SettingsTabs/General/ArchivedChatsModal.tsx rename client/src/components/Nav/SettingsTabs/General/{General.tsx => Selectors.tsx} (65%) delete mode 100644 client/src/components/Nav/SettingsTabs/Personalization.tsx delete mode 100644 client/src/components/Nav/SettingsTabs/Speech/Speech.tsx diff --git a/client/src/components/Audio/Voices.tsx b/client/src/components/Audio/Voices.tsx index f41d57ac26..5d1d843b58 100644 --- a/client/src/components/Audio/Voices.tsx +++ b/client/src/components/Audio/Voices.tsx @@ -6,7 +6,7 @@ import { useLocalize, useTTSBrowser, useTTSExternal } from '~/hooks'; import { logger } from '~/utils'; import store from '~/store'; -export function BrowserVoiceDropdown() { +export function BrowserVoiceDropdown({ disabled = false }: { disabled?: boolean }) { const localize = useLocalize(); const { voices = [] } = useTTSBrowser(); const [voice, setVoice] = useRecoilState(store.voice); @@ -33,12 +33,13 @@ export function BrowserVoiceDropdown() { testId="BrowserVoiceDropdown" className="z-50" aria-labelledby={labelId} + disabled={disabled} /> ); } -export function ExternalVoiceDropdown() { +export function ExternalVoiceDropdown({ disabled = false }: { disabled?: boolean }) { const localize = useLocalize(); const { voices = [] } = useTTSExternal(); const [voice, setVoice] = useRecoilState(store.voice); @@ -65,6 +66,7 @@ export function ExternalVoiceDropdown() { testId="ExternalVoiceDropdown" className="z-50" aria-labelledby={labelId} + disabled={disabled} /> ); diff --git a/client/src/components/Nav/AccountSettings.tsx b/client/src/components/Nav/AccountSettings.tsx index 5ced6db89a..efd0657542 100644 --- a/client/src/components/Nav/AccountSettings.tsx +++ b/client/src/components/Nav/AccountSettings.tsx @@ -1,7 +1,8 @@ import { useState, memo, useRef } from 'react'; import * as Menu from '@ariakit/react/menu'; -import { FileText, LogOut } from 'lucide-react'; +import { FileText, Archive, LogOut } from 'lucide-react'; import { LinkIcon, GearIcon, DropdownMenuSeparator, Avatar } from '@librechat/client'; +import { ArchivedChatsModal } from '~/components/Nav/SettingsTabs/General/ArchivedChatsModal'; import { MyFilesModal } from '~/components/Chat/Input/Files/MyFilesModal'; import { useGetStartupConfig, useGetUserBalance } from '~/data-provider'; import { useAuthContext } from '~/hooks/AuthContext'; @@ -17,6 +18,7 @@ function AccountSettings({ collapsed = false }: { collapsed?: boolean }) { }); const [showSettings, setShowSettings] = useState(false); const [showFiles, setShowFiles] = useState(false); + const [showArchived, setShowArchived] = useState(false); const accountSettingsButtonRef = useRef(null); return ( @@ -72,6 +74,10 @@ function AccountSettings({ collapsed = false }: { collapsed?: boolean }) {