diff --git a/apps/ui/src/components/site-overview-view/index.test.tsx b/apps/ui/src/components/site-overview-view/index.test.tsx
index 3837e8e6be..6d1068f7a2 100644
--- a/apps/ui/src/components/site-overview-view/index.test.tsx
+++ b/apps/ui/src/components/site-overview-view/index.test.tsx
@@ -18,6 +18,7 @@ import {
} from '@/data/queries/use-sites';
import { useWordPressVersions, useWpVersion } from '@/data/queries/use-wordpress-versions';
import { useOffline } from '@/hooks/use-offline';
+import { useThemeDetails } from '@/hooks/use-theme-details';
import styles from './style.module.css';
import { SiteOverviewView } from './index';
import type { SiteDetails } from '@/data/core';
@@ -97,6 +98,10 @@ vi.mock( '@/hooks/use-offline', () => ( {
useOffline: vi.fn(),
} ) );
+vi.mock( '@/hooks/use-theme-details', () => ( {
+ useThemeDetails: vi.fn(),
+} ) );
+
vi.mock( '@/hooks/use-sidebar-collapsed', () => ( {
useSidebarCollapsed: useSidebarCollapsedMock,
} ) );
@@ -118,6 +123,7 @@ const useSitesMock = vi.mocked( useSites, { partial: true } );
const useStartSiteMock = vi.mocked( useStartSite, { partial: true } );
const useUpdateSiteMock = vi.mocked( useUpdateSite, { partial: true } );
const useOfflineMock = vi.mocked( useOffline );
+const useThemeDetailsMock = vi.mocked( useThemeDetails );
const useWordPressVersionsMock = vi.mocked( useWordPressVersions, { partial: true } );
const useWpVersionMock = vi.mocked( useWpVersion, { partial: true } );
const useXdebugEnabledSiteMock = vi.mocked( useXdebugEnabledSite, { partial: true } );
@@ -150,6 +156,9 @@ describe( 'SiteOverviewView', () => {
} );
useConnectorMock.mockReturnValue( { openSiteUrl } );
+ useThemeDetailsMock.mockImplementation( ( site ) =>
+ site.themeDetails ? { state: 'ready', details: site.themeDetails } : { state: 'unknown' }
+ );
useAgenticFeaturesMock.mockReturnValue( {
enabled: true,
chatEnabled: true,
@@ -476,6 +485,26 @@ describe( 'SiteOverviewView', () => {
expect( screen.queryByText( 'Site Editor' ) ).not.toBeInTheDocument();
} );
+ it( 'holds theme-dependent shortcuts while theme details load', () => {
+ useThemeDetailsMock.mockReturnValue( { state: 'loading' } );
+
+ const { container } = renderView();
+
+ expect( screen.queryByText( 'Site Editor' ) ).not.toBeInTheDocument();
+ expect( screen.queryByText( 'Customizer' ) ).not.toBeInTheDocument();
+ expect( screen.queryByText( 'Media Library' ) ).not.toBeInTheDocument();
+ expect( container.querySelectorAll( `.${ styles.buttonSkeleton }` ) ).toHaveLength( 7 );
+ } );
+
+ it( 'retains the Classic fallback when theme details are unavailable', () => {
+ useThemeDetailsMock.mockReturnValue( { state: 'unknown' } );
+
+ renderView();
+
+ expect( screen.getByText( 'Customizer' ) ).toBeVisible();
+ expect( screen.queryByText( 'Site Editor' ) ).not.toBeInTheDocument();
+ } );
+
// Rendered without a SessionUIProvider, so the open-site-url hook takes
// its browser fallback path; inside the app these open the preview panel.
it( 'routes shortcuts through the connector', async () => {
diff --git a/apps/ui/src/components/site-overview-view/index.tsx b/apps/ui/src/components/site-overview-view/index.tsx
index 96c242a8fe..c6ab7bad4d 100644
--- a/apps/ui/src/components/site-overview-view/index.tsx
+++ b/apps/ui/src/components/site-overview-view/index.tsx
@@ -26,6 +26,7 @@ import { useIsSiteStarting, useIsSiteStopping, useSites } from '@/data/queries/u
import { useOpenSiteUrl } from '@/hooks/use-open-site-url';
import { useSidebarCollapsed } from '@/hooks/use-sidebar-collapsed';
import { useSiteManagementActions } from '@/hooks/use-site-management-actions';
+import { useThemeDetails } from '@/hooks/use-theme-details';
import { useTrafficLightSpace } from '@/hooks/use-traffic-light-space';
import styles from './style.module.css';
import type { SiteSettingsTabId } from '@/components/site-settings-view';
@@ -105,11 +106,25 @@ function OverviewButton( {
);
}
-function ButtonSection( { title, children }: { title: string; children: ReactNode } ) {
+function ButtonSection( {
+ title,
+ loadingCount = 0,
+ children,
+}: {
+ title: string;
+ loadingCount?: number;
+ children: ReactNode;
+} ) {
return (
{ title }
-