Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions src/components/site-content-tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import { ContentTabSettings } from 'src/components/content-tab-settings';
import Header from 'src/components/header';
import { SiteIsBeingCreated } from 'src/components/site-is-being-created';
import { MIN_WIDTH_CLASS_TO_MEASURE } from 'src/constants';
import { TabName, useContentTabs } from 'src/hooks/use-content-tabs';
import { TabName } from 'src/hooks/use-content-tabs';
import { useEffectiveTab } from 'src/hooks/use-effective-tab';
import { useImportExport } from 'src/hooks/use-import-export';
import { useSiteDetails } from 'src/hooks/use-site-details';
import { cx } from 'src/lib/cx';
Expand All @@ -18,16 +19,16 @@ import { ContentTabSync } from 'src/modules/sync';
export function SiteContentTabs() {
const { selectedSite, siteCreationMessages } = useSiteDetails();
const { importState } = useImportExport();
const { tabs, selectedTab, setSelectedTab } = useContentTabs();
const { effectiveTab, selectedTab, setSelectedTab, tabs } = useEffectiveTab();
const { __ } = useI18n();

// Remount: Avoid focus loss on user tab changes (no remount),
// but remount on programmatic changes and site switches so initial tab/content state resets.
const [ keyCounter, setKeyCounter ] = useState( 0 );
const [ programmaticTab, setProgrammaticTab ] = useState( selectedTab );
const [ programmaticTab, setProgrammaticTab ] = useState( effectiveTab );
const lastChangeWasUser = useRef( false );
const isFirstRender = useRef( true );
const prevSelectedTab = useRef( selectedTab );
const prevSelectedTab = useRef( effectiveTab );

useEffect( () => {
if ( isFirstRender.current ) {
Expand Down Expand Up @@ -83,19 +84,19 @@ export function SiteContentTabs() {
onSelect={ ( tabName ) => {
// Mark this as a user-initiated change BEFORE calling setSelectedTab
// so the useEffect can detect it was user-initiated
if ( tabName !== selectedTab ) {
if ( tabName !== effectiveTab ) {
lastChangeWasUser.current = true;
}
setSelectedTab( tabName as TabName );
} }
initialTabName={ selectedTab }
initialTabName={ effectiveTab }
key={ `${ selectedSite.id }-${ keyCounter }-${ programmaticTab }` }
>
{ ( { name } ) => (
<div
className={ cx(
'h-full overflow-y-auto',
selectedTab === 'assistant' && 'bg-gray-50'
effectiveTab === 'assistant' && 'bg-gray-50'
) }
style={ {
scrollbarWidth: 'thin',
Expand Down
34 changes: 34 additions & 0 deletions src/hooks/use-effective-tab.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { useEffect, useState } from 'react';
import { TabName, useContentTabs } from 'src/hooks/use-content-tabs';
import { useSiteDetails } from 'src/hooks/use-site-details';

/**
* Hook that computes the effective tab, handling the case where a site is deleted.
* When the previously selected site is deleted, automatically resets to 'overview'.
*/
export function useEffectiveTab() {
const { selectedSite, sites } = useSiteDetails();
const { selectedTab, setSelectedTab, tabs } = useContentTabs();

// Track previous site ID in state so we can safely access it during render
const [ prevSiteId, setPrevSiteId ] = useState( selectedSite?.id );

// Compute effective tab at render time
// This ensures TabPanel gets the correct initialTabName on the first render after deletion
const siteChanged = prevSiteId !== selectedSite?.id;
const prevSiteWasDeleted = prevSiteId && ! sites.some( ( site ) => site.id === prevSiteId );
const effectiveTab = siteChanged && prevSiteWasDeleted ? 'overview' : selectedTab;

// Update prevSiteId and sync selectedTab after render
useEffect( () => {
if ( siteChanged ) {
setPrevSiteId( selectedSite?.id );
}

if ( effectiveTab !== selectedTab ) {
setSelectedTab( effectiveTab as TabName );
}
}, [ siteChanged, selectedSite?.id, effectiveTab, selectedTab, setSelectedTab ] );

return { effectiveTab, selectedTab, setSelectedTab, tabs };
}
14 changes: 6 additions & 8 deletions src/hooks/use-site-details.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -238,19 +238,17 @@ export function SiteDetailsProvider( { children }: SiteDetailsProviderProps ) {
await deleteSite( id, removeLocal );
const newSites = await getIpcApi().getSiteDetails();
setSites( newSites );
// Only change selection if the currently selected site no longer exists
// Use functional update to access current selectedSiteId value
// Tab reset is handled in SiteContentTabs when it detects the previous site was deleted
setSelectedSiteId( ( currentSelectedId ) => {
const selectedSiteStillExists = newSites.some( ( site ) => site.id === currentSelectedId );
if ( selectedSiteStillExists ) {
return currentSelectedId;
if ( ! selectedSiteStillExists ) {
return newSites.length ? newSites[ 0 ].id : '';
}
return newSites.length ? newSites[ 0 ].id : '';
return currentSelectedId;
} );
if ( selectedTab !== 'overview' ) {
setSelectedTab( 'overview' );
}
},
[ deleteSite, setSelectedSiteId, selectedTab, setSelectedTab ]
[ deleteSite, setSelectedSiteId ]
);

const createSite = useCallback(
Expand Down