diff --git a/app/src/components/VoiceProfiles/ProfileCard.tsx b/app/src/components/VoiceProfiles/ProfileCard.tsx index c55382d5..b3d5ffee 100644 --- a/app/src/components/VoiceProfiles/ProfileCard.tsx +++ b/app/src/components/VoiceProfiles/ProfileCard.tsx @@ -41,6 +41,12 @@ export function ProfileCard({ profile, disabled }: ProfileCardProps) { const isSelected = selectedProfileId === profile.id; const handleSelect = () => { + // If disabled but already selected, bounce the selection to re-trigger engine auto-switch + if (disabled && isSelected) { + setSelectedProfileId(null); + setTimeout(() => setSelectedProfileId(profile.id), 0); + return; + } setSelectedProfileId(isSelected ? null : profile.id); }; diff --git a/app/src/components/VoiceProfiles/ProfileList.tsx b/app/src/components/VoiceProfiles/ProfileList.tsx index 51d4469d..3c18a296 100644 --- a/app/src/components/VoiceProfiles/ProfileList.tsx +++ b/app/src/components/VoiceProfiles/ProfileList.tsx @@ -20,17 +20,20 @@ export function ProfileList() { // Scroll to the selected profile after engine/sort changes useEffect(() => { if (!selectedProfileId) return; - // Wait a frame for the DOM to update after re-sort - requestAnimationFrame(() => { + let timeoutId: ReturnType | null = null; + const rafId = requestAnimationFrame(() => { const el = cardRefs.current.get(selectedProfileId); if (!el) return; // Temporarily apply scroll-margin so it doesn't land flush at the top el.style.scrollMarginTop = '180px'; el.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'nearest' }); - // Clean up after scroll completes - setTimeout(() => { el.style.scrollMarginTop = ''; }, 500); + timeoutId = setTimeout(() => { el.style.scrollMarginTop = ''; }, 500); }); + return () => { + cancelAnimationFrame(rafId); + if (timeoutId) clearTimeout(timeoutId); + }; }, [selectedProfileId, selectedEngine]); if (isLoading) {