Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
7b9b099
Explore logout experience, first go
Jul 13, 2026
95afbe0
Merge branch 'trunk' of github.com:Automattic/studio into add/logout-…
Jul 14, 2026
ec3d525
Fix non-existent font
Jul 14, 2026
42ce030
Adjust settings
Jul 14, 2026
1e71a43
Adjust remaining elements
Jul 14, 2026
5b721a9
Handle no sites experience
Jul 14, 2026
2e73f87
Merge branch 'trunk' of github.com:Automattic/studio into add/logout-…
Jul 15, 2026
8c45b50
Use agentic mock
Jul 15, 2026
2973fcb
Merge branch 'trunk' of github.com:Automattic/studio into add/logout-…
Jul 16, 2026
979418c
Fix unit tests
Jul 16, 2026
e3b2570
Adjust styles
Jul 16, 2026
f54529b
Merge branch 'trunk' of github.com:Automattic/studio into add/logout-…
Jul 17, 2026
72a67f0
Ensure we don't fetch sites for logged out users
Jul 17, 2026
b20a701
Remove unrelated changes
Jul 17, 2026
9d9cb50
Fix unrelated changes
Jul 17, 2026
5f85bf5
Fix sidebar header, preview, and publish behavior when logged out
Jul 17, 2026
89da0ac
Remove unrelated data-liberation dist changes
Jul 17, 2026
37fa839
Remove stale data-liberation chunk files deleted on trunk
Jul 17, 2026
9d2043b
Distinguish auth loading from signed-out in agentic features
bcotrim Jul 20, 2026
280922e
Move signin banner into new default Overview settings tab
bcotrim Jul 20, 2026
388b307
Return to the chat screen after logging in from the signin banner
bcotrim Jul 20, 2026
c76339d
Rename supportsAgenticOptOut to agenticRequiresAuth
bcotrim Jul 20, 2026
e425ac3
Add pending state to the dropdown Log in button
bcotrim Jul 20, 2026
b4776af
Hide preview snapshots while signed out
bcotrim Jul 20, 2026
328973c
Add pending state to the welcome Sign up button
bcotrim Jul 20, 2026
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
54 changes: 54 additions & 0 deletions apps/ui/src/components/agentic-signin-banner/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { useNavigate } from '@tanstack/react-router';
import { __ } from '@wordpress/i18n';
import { Button } from '@wordpress/ui';
import { useEffect, useRef } from 'react';
import { useAgenticFeatures } from '@/data/queries/use-agentic-features';
import { useLogin } from '@/data/queries/use-auth-user';
import styles from './style.module.css';
import type { AgenticFeatureReason } from '@/data/queries/use-agentic-features';

export function AgenticSigninBanner() {
const { enabled, reason } = useAgenticFeatures();
const login = useLogin();
const navigate = useNavigate();

// `authenticate()` resolves when the browser opens, not when OAuth
// completes, so a finished login only surfaces here as a signed-out →
// enabled transition. Send the user back to the chat screen when it does.
const previousReasonRef = useRef< AgenticFeatureReason >( null );
useEffect( () => {
const previous = previousReasonRef.current;
previousReasonRef.current = reason;
if ( enabled && previous === 'signed-out' ) {
void navigate( { to: '/' } );
}
}, [ enabled, reason, navigate ] );

if ( reason !== 'signed-out' ) {
return null;
}

return (
<section className={ styles.root } aria-label={ __( 'Sign in to Studio' ) }>
<div className={ styles.text }>
<h2 className={ styles.heading }>{ __( 'Sign in to do more with Studio' ) }</h2>
<ul className={ styles.benefits }>
<li>{ __( 'Chat with a WordPress expert that builds and edits your site for you' ) }</li>
<li>{ __( 'Share your work instantly with preview links' ) }</li>
<li>{ __( "Publish to a real WordPress.com site when you're ready" ) }</li>
</ul>
</div>
<div className={ styles.actions }>
<Button
type="button"
variant="solid"
tone="brand"
loading={ login.isPending }
onClick={ () => login.mutate() }
>
{ __( 'Log in with WordPress.com' ) }
</Button>
</div>
</section>
);
}
32 changes: 32 additions & 0 deletions apps/ui/src/components/agentic-signin-banner/style.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
.root {
display: flex;
align-items: flex-start;
gap: var(--wpds-dimension-padding-lg);
padding: var(--wpds-dimension-padding-lg);
border-radius: var(--wpds-border-radius-sm);
border: 1px solid var(--wpds-color-stroke-surface-neutral);
background: var(--wpds-color-bg-surface-neutral-weak);
}

.text {
flex: 1 1 auto;
}

.heading {
margin: 0;
font-size: var(--wpds-typography-font-size-md);
font-weight: var(--wpds-typography-font-weight-medium);
}

.benefits {
margin: 8px 0 0;
padding: 0 0 0 20px;
font-size: var(--wpds-typography-font-size-sm);
color: var(--wpds-color-fg-content-neutral-weak);
line-height: 1.5;
}

.actions {
flex: 0 0 auto;
align-self: center;
}
8 changes: 8 additions & 0 deletions apps/ui/src/components/site-dropdown/main-view.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@ vi.mock( '@/data/queries/use-connected-wpcom-sites', () => ( {
useConnectedWpcomSites: () => ( { data: connectedSites } ),
} ) );

vi.mock( '@/data/queries/use-agentic-features', () => ( {
useAgenticFeatures: vi.fn( () => ( { enabled: true, reason: null, isReady: true } ) ),
} ) );

vi.mock( '@/data/queries/use-auth-user', () => ( {
useLogin: () => ( { mutate: vi.fn(), isPending: false } ),
} ) );

vi.mock( '@/data/queries/use-preview-site', () => ( {
usePublishPreviewSite: () => ( { isPending: false, mutate: vi.fn() } ),
} ) );
Expand Down
35 changes: 25 additions & 10 deletions apps/ui/src/components/site-dropdown/main-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { clsx } from 'clsx';
import { useMemo } from 'react';
import * as Menu from '@/components/menu';
import { useConnector } from '@/data/core';
import { useAgenticFeatures } from '@/data/queries/use-agentic-features';
import { useLogin } from '@/data/queries/use-auth-user';
import { useConnectedWpcomSites } from '@/data/queries/use-connected-wpcom-sites';
import { usePublishPreviewSite } from '@/data/queries/use-preview-site';
import {
Expand Down Expand Up @@ -72,6 +74,8 @@ function useIsSiteSyncing( siteId: string ): { push: boolean; pull: boolean } {

export function MainView( { site, activity, onSetupClick, onDisconnectClick }: Props ) {
const connector = useConnector();
const { enabled: agenticEnabled } = useAgenticFeatures();
const login = useLogin();
const { data: snapshots } = useSnapshots();
const { data: connectedSites } = useConnectedWpcomSites( site.id );

Expand Down Expand Up @@ -240,7 +244,7 @@ export function MainView( { site, activity, onSetupClick, onDisconnectClick }: P
className={ styles.rowActionButton }
loading={ isPreviewPending }
loadingAnnouncement={ __( 'Updating preview' ) }
disabled={ isSyncing }
disabled={ isSyncing || ! agenticEnabled }
focusableWhenDisabled
onClick={ handlePreviewClick }
/>
Expand All @@ -250,13 +254,17 @@ export function MainView( { site, activity, onSetupClick, onDisconnectClick }: P
) : (
<EnvironmentActionPanel
title={ __( 'Preview' ) }
copy={ __( 'Share a review link for this version.' ) }
copy={
agenticEnabled
? __( 'Share a review link for this version.' )
: __( 'Sign in to share a review link.' )
}
buttonLabel={ __( 'Share' ) }
variant="outline"
tone="neutral"
loading={ isPreviewPending }
loadingAnnouncement={ __( 'Creating preview' ) }
disabled={ isSyncing }
disabled={ isSyncing || ! agenticEnabled }
onClick={ handlePreviewClick }
/>
) }
Expand All @@ -278,7 +286,7 @@ export function MainView( { site, activity, onSetupClick, onDisconnectClick }: P
icon={ arrowDown }
label={ isPullPending ? __( 'Pulling from live' ) : __( 'Pull from live' ) }
className={ styles.rowActionButton }
disabled={ isSyncing }
disabled={ isSyncing || ! agenticEnabled }
focusableWhenDisabled
onClick={ handlePullClick }
/>
Expand All @@ -289,20 +297,23 @@ export function MainView( { site, activity, onSetupClick, onDisconnectClick }: P
icon={ arrowUp }
label={ isPushPending ? __( 'Pushing to live' ) : __( 'Push to live' ) }
className={ styles.rowActionButton }
disabled={ isSyncing }
disabled={ isSyncing || ! agenticEnabled }
focusableWhenDisabled
onClick={ handlePushClick }
/>
<Menu.SubmenuRoot>
<Menu.SubmenuTrigger
className={ styles.moreMenuTrigger }
disabled={ isSyncing }
disabled={ isSyncing || ! agenticEnabled }
aria-label={ __( 'More live site actions' ) }
>
<Icon icon={ moreHorizontal } size={ 16 } aria-hidden="true" />
</Menu.SubmenuTrigger>
<Menu.Popup side="right" align="start" className={ styles.moreMenuPopup }>
<Menu.Item disabled={ isSyncing } onClick={ onDisconnectClick }>
<Menu.Item
disabled={ isSyncing || ! agenticEnabled }
onClick={ onDisconnectClick }
>
{ __( 'Disconnect' ) }
</Menu.Item>
</Menu.Popup>
Expand All @@ -313,12 +324,16 @@ export function MainView( { site, activity, onSetupClick, onDisconnectClick }: P
) : (
<EnvironmentActionPanel
title={ __( 'Live' ) }
copy={ __( 'No connected site.' ) }
buttonLabel={ __( 'Publish' ) }
copy={
agenticEnabled ? __( 'No connected site.' ) : __( 'Sign in to publish your site.' )
}
buttonLabel={ agenticEnabled ? __( 'Publish' ) : __( 'Log in' ) }
variant="solid"
tone="brand"
loading={ ! agenticEnabled && login.isPending }
loadingAnnouncement={ __( 'Opening login page' ) }
disabled={ isSyncing }
onClick={ onSetupClick }
onClick={ agenticEnabled ? onSetupClick : () => login.mutate() }
/>
) }
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
.picker {
display: flex;
flex-direction: column;
min-height: 160px;
}

.header {
Expand Down
54 changes: 28 additions & 26 deletions apps/ui/src/components/site-dropdown/publish-picker-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { __ } from '@wordpress/i18n';
import { chevronLeft, plus } from '@wordpress/icons';
import { Icon, IconButton } from '@wordpress/ui';
import { useConnector } from '@/data/core';
import { useAuthUser } from '@/data/queries/use-auth-user';
import { connectedWpcomSitesQueryKey } from '@/data/queries/use-connected-wpcom-sites';
import { usePickableWpcomSites } from '@/data/queries/use-wpcom-sites';
import styles from './publish-picker-view.module.css';
Expand All @@ -20,8 +21,7 @@ type Props = {
export function PublishPickerView( { site, onClose }: Props ) {
const connector = useConnector();
const queryClient = useQueryClient();
// No `enabled` guard needed — this component only mounts when the picker
// view is active, which is the same gating condition.
const { data: authUser } = useAuthUser();
const pickableSites = usePickableWpcomSites();

const openExternal = ( url: string ) => {
Expand Down Expand Up @@ -70,30 +70,32 @@ export function PublishPickerView( { site, onClose }: Props ) {
/>
<span className={ styles.title }>{ __( 'Publish this site' ) }</span>
</div>
<div className={ styles.body }>
{ pickableSites.isLoading ? (
<div className={ styles.status }>{ __( 'Loading sites…' ) }</div>
) : pickableSites.data && pickableSites.data.length > 0 ? (
<ul className={ styles.list }>
{ pickableSites.data.map( ( candidate ) => (
<li key={ candidate.id }>
<button
type="button"
className={ styles.item }
onClick={ () => void handlePickSite( candidate ) }
>
<span className={ styles.itemName }>{ candidate.name || candidate.url }</span>
<span className={ styles.itemUrl }>{ stripProtocol( candidate.url ) }</span>
</button>
</li>
) ) }
</ul>
) : (
<div className={ styles.status }>
{ __( 'No WordPress.com sites available to publish to.' ) }
</div>
) }
</div>
{ authUser ? (
<div className={ styles.body }>
{ pickableSites.isLoading ? (
<div className={ styles.status }>{ __( 'Loading sites…' ) }</div>
) : pickableSites.data && pickableSites.data.length > 0 ? (
<ul className={ styles.list }>
{ pickableSites.data.map( ( candidate ) => (
<li key={ candidate.id }>
<button
type="button"
className={ styles.item }
onClick={ () => void handlePickSite( candidate ) }
>
<span className={ styles.itemName }>{ candidate.name || candidate.url }</span>
<span className={ styles.itemUrl }>{ stripProtocol( candidate.url ) }</span>
</button>
</li>
) ) }
</ul>
) : (
<div className={ styles.status }>
{ __( 'No WordPress.com sites available to publish to.' ) }
</div>
) }
</div>
) : null }
<button type="button" className={ styles.create } onClick={ handleCreateNew }>
<Icon icon={ plus } size={ 16 } />
<span>{ __( 'Create a new WordPress.com site…' ) }</span>
Expand Down
4 changes: 4 additions & 0 deletions apps/ui/src/components/site-list/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ vi.mock( '@/data/queries/use-sites', () => ( {
useUpdateSitesSortOrder: vi.fn(),
} ) );

vi.mock( '@/data/queries/use-agentic-features', () => ( {
useAgenticFeatures: vi.fn( () => ( { enabled: true, reason: null, isReady: true } ) ),
} ) );

vi.mock( '@/data/queries/use-user-preferences', () => ( {
useUserPreferences: vi.fn(),
} ) );
Expand Down
12 changes: 12 additions & 0 deletions apps/ui/src/components/site-list/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { SidebarButton } from '@/components/sidebar-button';
import { deriveSiteStatus } from '@/components/site-dropdown/utils';
import { useConnector } from '@/data/core';
import { useSiteAgentActivity, type SiteAgentActivity } from '@/data/queries/use-agent-run';
import { useAgenticFeatures } from '@/data/queries/use-agentic-features';
import { useSessions } from '@/data/queries/use-sessions';
import {
useCopySite,
Expand Down Expand Up @@ -445,11 +446,13 @@ function SiteSection( {
isChatActive,
isContextActive,
hasUnreadUpdate,
agenticEnabled,
}: {
row: SiteRow;
isChatActive: boolean;
isContextActive: boolean;
hasUnreadUpdate: boolean;
agenticEnabled: boolean;
} ) {
const { site, latestSession } = row;
const navigate = useNavigate();
Expand All @@ -469,6 +472,13 @@ function SiteSection( {
? 'new-message'
: 'idle';
const handleOpenSite = () => {
if ( ! agenticEnabled ) {
void navigate( {
to: '/sites/$siteId/settings',
params: { siteId: site.id },
} );
return;
}
if ( latestSession ) {
void navigate( {
to: '/sessions/$sessionId',
Expand Down Expand Up @@ -539,6 +549,7 @@ function findSessionSiteKey(
export function SiteList() {
const { data: sites, isLoading: sitesLoading } = useSites();
const { data: sessions, isLoading: sessionsLoading } = useSessions();
const { enabled: agenticEnabled } = useAgenticFeatures();
const params = useParams( { strict: false } ) as { sessionId?: string; siteId?: string };
const pathname = useRouterState( { select: ( state ) => state.location.pathname } );
const activeSessionId = params.sessionId;
Expand Down Expand Up @@ -636,6 +647,7 @@ export function SiteList() {
isChatActive={ row.site.id === activeChatSiteKey }
isContextActive={ row.site.id === activeContextSiteKey }
hasUnreadUpdate={ unreadSiteIds.has( row.site.id ) }
agenticEnabled={ agenticEnabled }
/>
);

Expand Down
Loading
Loading