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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .codex/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[mcp_servers.wpcom-mcp]
url = "https://public-api.wordpress.com/wpcom/v2/mcp/v1"
14 changes: 14 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ WordPress Studio - Electron desktop app for managing local WordPress sites. Buil
**Test**: `npm test [-- path/to/test.test.ts]` | `npm run e2e`
**Quality**: `npx eslint --fix <files>` (lint and format ONLY modified files)
**IMPORTANT - Post-Change Verification**: After applying code changes, always run the linter and format modified files (`npx eslint --fix <files>`), the type checker (`npm run typecheck`) and run relevant tests (`npm test [-- path/to/test]`) before considering the work complete.
**IMPORTANT - `apps/ui` Visual Verification**: Do not treat `apps/ui` as a standalone web app for browser/Playwright visual checks. It is an Electron renderer that expects Studio IPC and runtime context. Do not start the Vite dev server, open localhost, mock `window.ipcApi`, or request escalated permissions for visual browser testing unless the user explicitly asks for that. For `apps/ui` changes, run code-level checks only; the user will manually verify the UI in Studio.
**Package**: `npm run make` (builds installers for current platform)

**IMPORTANT - Hot Reload**: Renderer auto-reloads, Main process needs restart (or `rs` in terminal). Changes to Main process IPC handlers require full restart.
Expand All @@ -29,6 +30,7 @@ WordPress Studio - Electron desktop app for managing local WordPress sites. Buil
## Directory Structure

**`/apps/studio/src`**: Main (index.ts, ipc-handlers.ts, site-server.ts, storage/, lib/) | Renderer (components/, hooks/, stores/) | modules/ (sync, cli, user-settings, preview-site)
**`/apps/ui`**: New portable renderer UI package for Studio. Contains shared UI/data code plus two active UI explorations: `src/ui-classic/` and `src/ui-desks/`.
**`/apps/cli`**: index.ts, commands/ (auth, preview, site), lib/ (appdata, i18n, browser)
**`/tools/common`**: Shared lib/ (fs-utils, port-finder, oauth), types/, translations/
**`/tools/eslint-plugin-studio`**: eslint-plugin-studio
Expand All @@ -39,6 +41,18 @@ WordPress Studio - Electron desktop app for managing local WordPress sites. Buil
**CliServerProcess**: Desktop spawns CLI as child process (`apps/studio/src/modules/cli/lib/cli-server-process.ts`)
**Redux Stores**: chat, sync, connectedSites, snapshot, onboarding | RTK Query APIs: wpcomApi, installedAppsApi, wordpressVersionsApi
**SiteServer** (`apps/studio/src/site-server.ts`): Manages site instances, server start/stop, SSL certs, ports
**New UI Connector Pattern** (`apps/ui/src/data/core/`): UI data access goes through the `Connector` interface and TanStack Query hooks in `apps/ui/src/data/queries/`. Keep UI code environment-agnostic; do not call Electron IPC directly from components when a connector/query hook is the right boundary.
**New UI Mode Selection** (`apps/ui/src/app/use-ui-mode.ts`): `studio-ui-mode=desks` loads `ui-desks`, `studio-ui-mode=agentic` loads `ui-classic`, and the default mode is currently `desks`.

## New UI Explorations (`apps/ui`)

Studio has two parallel renderer explorations under `apps/ui`. When coding in this package, infer which UI the user means from the file path, route/component names, vocabulary in the request, and the current mode hook before making changes. If a request mentions sidebar, chat list, sessions, conversation view, or a traditional Studio shell, work in `apps/ui/src/ui-classic/` unless nearby shared components clearly own the behavior. If a request mentions desks, canvas, widgets, cards, site map, tldraw, spatial layout, annotations on the canvas, or infinite canvas behavior, work in `apps/ui/src/ui-desks/`.

**`ui-classic`**: A more traditional Studio UI with a left sidebar and a chat/session-focused workflow. It is optimized around site lists, sessions, conversations, queued prompts, composer controls, and familiar app navigation. Classic-specific routes live in `apps/ui/src/ui-classic/router/`, and classic chat UI lives in `apps/ui/src/ui-classic/components/session-view/`.

**`ui-desks`**: A more experimental spatial UI centered on an infinite canvas powered by `tldraw`. It organizes work as desks with widgets, surfaces, stacks, site previews, notes, posts, media, theme tools, and chat panels layered around the canvas. Desk-specific code lives in `apps/ui/src/ui-desks/`, with canvas/provider behavior under `desk/`, widget definitions under `widgets/`, and desk chrome under `chrome/`.

Shared UI primitives and data hooks that intentionally serve both explorations belong outside either mode-specific tree, such as `apps/ui/src/components/`, `apps/ui/src/hooks/`, `apps/ui/src/lib/`, and `apps/ui/src/data/`. Before moving shared behavior, check both explorations for consumers and preserve mode-specific styling/interaction expectations.

## Tech Stack

Expand Down
8 changes: 1 addition & 7 deletions apps/cli/commands/ai/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -315,12 +315,6 @@ export async function runCommand( options: {
const config = await readCliConfig();
let showCapabilitiesOnConnect = ! config.aiProvider;

// Studio Code Desktop defaults to WordPress.com provider.
if ( isJsonMode && showCapabilitiesOnConnect ) {
await switchProvider( 'wpcom', false );
showCapabilitiesOnConnect = false;
}

if ( showCapabilitiesOnConnect ) {
ui.showOnboarding();

Expand Down Expand Up @@ -694,7 +688,7 @@ export async function runCommand( options: {
export const registerCommand = ( yargs: StudioArgv ) => {
return yargs.command( {
command: '$0 [message]',
describe: __( 'Start an interactive AI chat to build WordPress sites' ),
describe: __( 'AI agent for building WordPress' ),
builder: ( yargs ) => {
let chain = yargs
.positional( 'message', {
Expand Down
103 changes: 0 additions & 103 deletions apps/cli/commands/ai/tests/index.test.ts

This file was deleted.

9 changes: 1 addition & 8 deletions apps/cli/commands/site/status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,14 +89,7 @@ export async function runCommand( siteFolder: string, format: 'table' | 'json' )
console.table( table.toString() );
} else {
const logData = Object.fromEntries(
siteData.flatMap( ( { jsonKey, value } ) =>
jsonKey === 'status'
? [
[ jsonKey, value ],
[ 'isOnline', isOnline ],
]
: [ [ jsonKey, value ] ]
)
siteData.map( ( { jsonKey, value } ) => [ jsonKey, value ] )
);

console.log( JSON.stringify( logData, null, 2 ) );
Expand Down
3 changes: 0 additions & 3 deletions apps/cli/commands/site/tests/status.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,6 @@ describe( 'CLI: studio site status', () => {
siteUrl: 'http://localhost:8080/',
sitePath: '/path/to/site',
status: '🔴 Offline',
isOnline: false,
phpVersion: '8.0',
wpVersion: '6.4',
xdebug: 'Disabled',
Expand Down Expand Up @@ -117,7 +116,6 @@ describe( 'CLI: studio site status', () => {
autoLoginUrl: 'http://localhost:8080/studio-auto-login?redirect_to=%2Fwp-admin%2F',
sitePath: '/path/to/site',
status: '🟢 Online',
isOnline: true,
phpVersion: '8.0',
wpVersion: '6.4',
xdebug: 'Disabled',
Expand Down Expand Up @@ -159,7 +157,6 @@ describe( 'CLI: studio site status', () => {
siteUrl: 'http://localhost:8080/',
sitePath: '/path/to/site',
status: '🔴 Offline',
isOnline: false,
wpVersion: '6.4',
xdebug: 'Disabled',
adminUsername: 'admin',
Expand Down
73 changes: 21 additions & 52 deletions apps/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,59 +126,28 @@ async function main() {
registerAiCommand( aiYargs );
const { registerRemoteSessionCommand } = await import( 'cli/commands/ai/remote-session' );
registerRemoteSessionCommand( aiYargs );
aiYargs.command(
'sessions',
__( 'List, resume, and delete code sessions' ),
async ( sessionsYargs ) => {
const [
{ registerCommand: registerAiSessionsDeleteCommand },
{ registerCommand: registerAiSessionsListCommand },
{ registerCommand: registerAiSessionsResumeCommand },
] = await Promise.all( [
import( 'cli/commands/ai/sessions/delete' ),
import( 'cli/commands/ai/sessions/list' ),
import( 'cli/commands/ai/sessions/resume' ),
] );
aiYargs.command( 'sessions', __( 'Manage code sessions' ), async ( sessionsYargs ) => {
const [
{ registerCommand: registerAiSessionsDeleteCommand },
{ registerCommand: registerAiSessionsListCommand },
{ registerCommand: registerAiSessionsResumeCommand },
] = await Promise.all( [
import( 'cli/commands/ai/sessions/delete' ),
import( 'cli/commands/ai/sessions/list' ),
import( 'cli/commands/ai/sessions/resume' ),
] );

sessionsYargs.option( 'path', {
hidden: true,
} );
registerAiSessionsDeleteCommand( sessionsYargs );
registerAiSessionsListCommand( sessionsYargs );
registerAiSessionsResumeCommand( sessionsYargs );
sessionsYargs
.version( false )
.demandCommand( 1, __( 'You must provide a valid code sessions command' ) );
}
);
aiYargs
.example( [
[ 'studio code', __( 'Start an interactive chat with the AI agent' ) ],
[
'studio code "Create a portfolio site"',
__( 'Start the agent with an initial message' ),
],
[
'studio code --json "Add a contact page"',
__( 'Run a single headless turn, printing NDJSON events' ),
],
[ 'studio code sessions list', __( 'List previous code sessions' ) ],
[ 'studio code sessions resume latest', __( 'Resume the most recent session' ) ],
] )
.epilogue(
[
__(
'Studio Code is an AI agent that builds WordPress sites: it creates and manages local and remote sites, builds themes, writes code, generates content, and publishes to WordPress.com.'
),
'',
sprintf(
/* translators: %s: Studio Code support documentation URL */
__( 'Learn more: %s' ),
'https://developer.wordpress.com/docs/developer-tools/studio/studio-code/'
),
].join( '\n' )
)
.version( false );
sessionsYargs.option( 'path', {
hidden: true,
} );
registerAiSessionsDeleteCommand( sessionsYargs );
registerAiSessionsListCommand( sessionsYargs );
registerAiSessionsResumeCommand( sessionsYargs );
sessionsYargs
.version( false )
.demandCommand( 1, __( 'You must provide a valid code sessions command' ) );
} );
aiYargs.version( false );
};
studioArgv.command(
'code',
Expand Down
2 changes: 1 addition & 1 deletion apps/studio/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline' 'wasm-unsafe-eval'; worker-src 'self' blob:; style-src 'self' 'unsafe-inline'; connect-src 'self' https://public-api.wordpress.com https://api.wordpress.org https://*.gravatar.com https://*.wp.com https://blueprintlibrary.wordpress.com https://blueprintslibraryv2.wpcomstaging.com https://unpkg.com https://cdn.jsdelivr.net ws://localhost:*; img-src 'self' https://*.gravatar.com https://*.wp.com https://blueprintlibrary.wordpress.com https://blueprintslibraryv2.wpcomstaging.com data:;">
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline' 'wasm-unsafe-eval'; worker-src 'self' blob:; style-src 'self' 'unsafe-inline'; connect-src 'self' https://public-api.wordpress.com https://api.wordpress.org https://*.gravatar.com https://*.wp.com https://blueprintlibrary.wordpress.com https://blueprintslibraryv2.wpcomstaging.com https://unpkg.com https://cdn.jsdelivr.net https://wordpress.github.io https://raw.githubusercontent.com ws://localhost:*; img-src 'self' https://*.gravatar.com https://*.wp.com https://blueprintlibrary.wordpress.com https://blueprintslibraryv2.wpcomstaging.com https://wordpress.github.io https://raw.githubusercontent.com data:;">
<title>WordPress Studio</title>
</head>
<body class="a8c-body">
Expand Down
Loading