@@ -11,6 +11,7 @@ import {
1111 BROWSER_IMPORT_UNAVAILABLE_COPY ,
1212 BrowserImportFailureReason ,
1313 BROWSER_PROFILE_MAX_COUNT ,
14+ type BrowserProfile ,
1415 BROWSER_PROFILE_NAME_MAX_LENGTH ,
1516 DEFAULT_BROWSER_AUTO_SHOW_FLOATING_PREVIEW ,
1617 DEFAULT_BROWSER_PROFILE_ID ,
@@ -54,6 +55,15 @@ import {
5455 MenuTrigger ,
5556} from "../ui/menu" ;
5657import { toastManager } from "../ui/toast" ;
58+ import {
59+ AlertDialog ,
60+ AlertDialogClose ,
61+ AlertDialogDescription ,
62+ AlertDialogFooter ,
63+ AlertDialogHeader ,
64+ AlertDialogPopup ,
65+ AlertDialogTitle ,
66+ } from "../ui/alert-dialog" ;
5767import { Button } from "../ui/button" ;
5868import { DraftInput } from "../ui/draft-input" ;
5969import { NumberField , NumberFieldGroup , NumberFieldInput } from "../ui/number-field" ;
@@ -523,11 +533,17 @@ function BrowserProfilesSetting({ disabled }: { readonly disabled: boolean }) {
523533 const updateSettings = useUpdatePrimarySettings ( ) ;
524534 const environmentId = usePrimaryEnvironment ( ) ?. environmentId ;
525535 const [ sources , setSources ] = useState < ReadonlyArray < BrowserImportSource > | null > ( null ) ;
536+ const [ profilePendingRemoval , setProfilePendingRemoval ] = useState < BrowserProfile | null > ( null ) ;
526537 const [ busy , setBusy ] = useState ( false ) ;
527538
528539 const profiles = resolveBrowserProfiles ( userProfiles ) ;
540+ // Incognito is deliberately not a row — it holds nothing to manage — so the
541+ // default has to resolve against the list that renders. A stored
542+ // `browserDefaultProfileId` of "incognito" would otherwise leave the section
543+ // with no Default badge at all.
544+ const listedProfiles = profiles . filter ( ( profile ) => profile . kind !== "incognito" ) ;
529545 const resolvedDefaultId =
530- findBrowserProfile ( profiles , defaultProfileId ) ?. id ?? DEFAULT_BROWSER_PROFILE_ID ;
546+ findBrowserProfile ( listedProfiles , defaultProfileId ) ?. id ?? DEFAULT_BROWSER_PROFILE_ID ;
531547
532548 const uniqueName = ( base : string ) => {
533549 const taken = new Set ( profiles . map ( ( profile ) => profile . name ) ) ;
@@ -573,6 +589,7 @@ function BrowserProfilesSetting({ disabled }: { readonly disabled: boolean }) {
573589 } ;
574590
575591 const removeProfile = ( id : string ) => {
592+ setProfilePendingRemoval ( null ) ;
576593 // Drop the partition's data too, otherwise a removed profile's cookies
577594 // stay on disk with nothing in the UI pointing at them.
578595 if ( environmentId ) {
@@ -587,6 +604,10 @@ function BrowserProfilesSetting({ disabled }: { readonly disabled: boolean }) {
587604
588605 const loadSources = ( ) => {
589606 if ( ! previewBridge ) return ;
607+ // Cleared first: availability changes while the app runs (quitting a
608+ // browser clears `browserRunning`), and showing the previous answer during
609+ // the refresh lets the user start an import the source no longer supports.
610+ setSources ( null ) ;
590611 void previewBridge
591612 . listBrowserImportSources ( )
592613 . then ( setSources )
@@ -728,70 +749,98 @@ function BrowserProfilesSetting({ disabled }: { readonly disabled: boolean }) {
728749 }
729750 >
730751 < div className = "mt-2 overflow-hidden rounded-lg border border-border/60" >
731- { profiles
732- . filter ( ( profile ) => profile . kind !== "incognito" )
733- . map ( ( profile , index ) => {
734- const builtIn = isBuiltInBrowserProfileId ( profile . id ) ;
735- const isDefault = profile . id === resolvedDefaultId ;
736- return (
737- < div
738- key = { profile . id }
739- className = { cn (
740- "flex items-center gap-3 px-3 py-2" ,
741- index > 0 && "border-t border-border/60" ,
752+ { listedProfiles . map ( ( profile , index ) => {
753+ const builtIn = isBuiltInBrowserProfileId ( profile . id ) ;
754+ const isDefault = profile . id === resolvedDefaultId ;
755+ return (
756+ < div
757+ key = { profile . id }
758+ className = { cn (
759+ "flex items-center gap-3 px-3 py-2" ,
760+ index > 0 && "border-t border-border/60" ,
761+ ) }
762+ >
763+ < span className = "flex min-w-0 flex-1 items-center gap-2" >
764+ { builtIn ? (
765+ < span className = "truncate text-sm text-foreground" > { profile . name } </ span >
766+ ) : (
767+ < DraftInput
768+ nativeInput
769+ size = "sm"
770+ className = "w-full max-w-56"
771+ aria-label = { `Rename ${ profile . name } ` }
772+ disabled = { disabled }
773+ maxLength = { BROWSER_PROFILE_NAME_MAX_LENGTH }
774+ value = { profile . name }
775+ onCommit = { ( next ) => renameProfile ( profile . id , next ) }
776+ />
742777 ) }
743- >
744- < span className = "flex min-w-0 flex-1 items-center gap-2" >
745- { builtIn ? (
746- < span className = "truncate text-sm text-foreground" > { profile . name } </ span >
747- ) : (
748- < DraftInput
749- nativeInput
750- size = "sm"
751- className = "w-full max-w-56"
752- aria-label = { `Rename ${ profile . name } ` }
778+ { isDefault ? < Badge > Default</ Badge > : null }
779+ </ span >
780+ < Menu >
781+ < MenuTrigger
782+ render = {
783+ < Button
784+ size = "icon-sm"
785+ variant = "ghost-muted"
753786 disabled = { disabled }
754- maxLength = { BROWSER_PROFILE_NAME_MAX_LENGTH }
755- value = { profile . name }
756- onCommit = { ( next ) => renameProfile ( profile . id , next ) }
787+ aria-label = { `${ profile . name } options` }
757788 />
758- ) }
759- { isDefault ? < Badge > Default</ Badge > : null }
760- </ span >
761- < Menu >
762- < MenuTrigger
763- render = {
764- < Button
765- size = "icon-sm"
766- variant = "ghost-muted"
767- disabled = { disabled }
768- aria-label = { `${ profile . name } options` }
769- />
770- }
789+ }
790+ >
791+ < MoreVertical />
792+ </ MenuTrigger >
793+ < MenuPopup align = "end" className = "min-w-44" >
794+ < MenuItem
795+ disabled = { isDefault }
796+ onClick = { ( ) => updateSettings ( { browserDefaultProfileId : profile . id } ) }
771797 >
772- < MoreVertical />
773- </ MenuTrigger >
774- < MenuPopup align = "end" className = "min-w-44" >
798+ Set as default
799+ </ MenuItem >
800+ < MenuItem onClick = { ( ) => clearProfileData ( profile . id , profile . name ) } >
801+ Clear cookies and cache
802+ </ MenuItem >
803+ { builtIn ? null : (
775804 < MenuItem
776- disabled = { isDefault }
777- onClick = { ( ) => updateSettings ( { browserDefaultProfileId : profile . id } ) }
805+ variant = "destructive"
806+ onClick = { ( ) => setProfilePendingRemoval ( profile ) }
778807 >
779- Set as default
780- </ MenuItem >
781- < MenuItem onClick = { ( ) => clearProfileData ( profile . id , profile . name ) } >
782- Clear cookies and cache
808+ Remove profile and data
783809 </ MenuItem >
784- { builtIn ? null : (
785- < MenuItem variant = "destructive" onClick = { ( ) => removeProfile ( profile . id ) } >
786- Remove profile and data
787- </ MenuItem >
788- ) }
789- </ MenuPopup >
790- </ Menu >
791- </ div >
792- ) ;
793- } ) }
810+ ) }
811+ </ MenuPopup >
812+ </ Menu >
813+ </ div >
814+ ) ;
815+ } ) }
794816 </ div >
817+ < AlertDialog
818+ open = { profilePendingRemoval !== null }
819+ onOpenChange = { ( open ) => {
820+ if ( ! open ) setProfilePendingRemoval ( null ) ;
821+ } }
822+ >
823+ < AlertDialogPopup >
824+ < AlertDialogHeader >
825+ < AlertDialogTitle > Remove “{ profilePendingRemoval ?. name } ”?</ AlertDialogTitle >
826+ < AlertDialogDescription >
827+ Its cookies, logins, and cache are deleted with it. Tabs open in this profile move to
828+ the default one.
829+ </ AlertDialogDescription >
830+ </ AlertDialogHeader >
831+ < AlertDialogFooter >
832+ < AlertDialogClose render = { < Button variant = "outline" /> } > Cancel</ AlertDialogClose >
833+ < Button
834+ variant = "destructive"
835+ onClick = { ( ) => {
836+ if ( profilePendingRemoval ) removeProfile ( profilePendingRemoval . id ) ;
837+ } }
838+ >
839+ Remove profile
840+ </ Button >
841+ </ AlertDialogFooter >
842+ </ AlertDialogPopup >
843+ </ AlertDialog >
795844 </ SettingsRow >
796845 ) ;
797846}
0 commit comments