Skip to content

Add the studio ui local web surface#3953

Open
youknowriad wants to merge 19 commits into
trunkfrom
claude/distracted-euclid-7036cd
Open

Add the studio ui local web surface#3953
youknowriad wants to merge 19 commits into
trunkfrom
claude/distracted-euclid-7036cd

Conversation

@youknowriad

@youknowriad youknowriad commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Related issues

Builds on the desktop/local backend convergence that landed separately — the @studio/common extractions for sessions, sites, the agent run-manager, the REST proxy, and the app-config accessor.

How AI was used in this PR

Built with Claude Code. The backend was first converged into transport-agnostic @studio/common modules (extracted and reviewed in separate PRs, now on trunk); this PR assembles the studio ui surface on top and was reconciled against trunk after those landed. The server, connector, and CLI command delegate to the shared modules rather than reimplementing anything. I reviewed the diff; UI/visual correctness still needs a human pass (see checklist).

Proposed Changes

Adds studio ui — a way to run the same agentic Studio UI the desktop shows, in a regular browser.

studio ui starts a small local web server (apps/local) that serves the existing apps/ui app over HTTP + SSE instead of Electron IPC, and opens it in the browser. It's the browser analog of the desktop app: same React UI, same sessions, same sites, same agent — only the transport differs. Every operation delegates to the shared @studio/common backend the desktop already uses, so behavior stays in lockstep across the two surfaces.

Browser login reuses the desktop app's existing WordPress.com OAuth client with a loopback redirect (no new client). The UI gains a small connector-capabilities layer so browser-only constraints (no native file dialogs, no traffic-light spacing, etc.) degrade gracefully.

User impact: a new studio ui command opens the agentic UI in a browser — useful where the desktop app isn't available or wanted, and the foundation for a hosted version later. No change to the desktop app's behavior.

Testing Instructions

  • npm run typecheck is clean for tools/common, apps/studio, apps/cli, apps/ui, apps/local.
  • npm run build:local --workspace=apps/ui produces dist-local.
  • End to end: npm run cli:build && node apps/cli/dist/cli/main.mjs ui — the server starts and the browser opens the agentic UI. Create/list sites, start a session, send a prompt, and (when logged in) sync.

Pre-merge Checklist

  • Have you checked for TypeScript, React or other console errors? — typecheck clean across all workspaces and dist-local builds; runtime console + visual still need a human pass per the note above.

🤖 Generated with Claude Code

youknowriad and others added 2 commits June 25, 2026 12:33
…ed @studio/common backend

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ared UI)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@youknowriad youknowriad marked this pull request as ready for review June 25, 2026 11:49
Comment thread apps/local/src/index.ts Fixed
Comment thread apps/local/src/index.ts Fixed
Comment thread apps/local/src/index.ts Fixed
Comment thread tools/common/lib/wordpress-rest.ts Fixed
Comment thread tools/common/sites/blueprint-extract.ts Fixed
…dia/read, contain temp deletes

Addresses the CodeQL alerts on PR #3953 (path injection, SSRF). The local server is loopback-only but reachable cross-origin from the browser, so: CORS is now allowlisted (disallowed origins rejected), the shared REST proxy validates that a request path stays within the site's REST root, the arbitrary-file /media/read endpoint is removed (unused), and temp-dir cleanup + blueprint extraction are path-contained.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comment thread apps/local/src/index.ts Dismissed
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@wpmobilebot

wpmobilebot commented Jun 25, 2026

Copy link
Copy Markdown
Collaborator

📊 Performance Test Results

Comparing 73db67f vs trunk

app-size

Metric trunk 73db67f Diff Change
App Size (Mac) 1316.77 MB 1316.88 MB +0.12 MB ⚪ 0.0%

site-editor

Metric trunk 73db67f Diff Change
load 1093 ms 755 ms 338 ms 🟢 -30.9%

site-startup

Metric trunk 73db67f Diff Change
siteCreation 6537 ms 6510 ms 27 ms ⚪ 0.0%
siteStartup 1863 ms 1861 ms 2 ms ⚪ 0.0%

Results are median values from multiple test runs.

Legend: 🟢 Improvement (faster) | 🔴 Regression (slower) | ⚪ No change (<50ms diff)

Both surfaces now record the agent's weekly/monthly unique-user stats against the same app.json store via a shared appBumpStatsProvider in @studio/common, so a user is counted once per period regardless of surface (previously studio ui wrote to the CLI's unrelated cli.json, which double-counted users on both). Only the surface differs, selecting the studio-code-ui-* vs studio-code-cliui-* groups; the provider is no longer injected per host.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Removes the "shared between the desktop app and the local server / now lives in @studio/common / re-export keeps imports working" commentary across the extracted modules and their desktop bindings; shared-module headers keep a one-line description and any genuine behavior notes (storage/lockfile, surface→stat-group mapping, the @sentry/core rationale).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…clid-7036cd

# Conflicts:
#	apps/cli/index.ts
#	apps/hosted/README.md
#	apps/hosted/src/index.ts
#	apps/studio/src/ipc-handlers.ts
#	apps/studio/src/lib/ai-session-placement.ts
#	apps/studio/src/lib/bump-stats.ts
#	apps/studio/src/modules/ai-agent/run-manager.ts
#	apps/studio/src/site-server.ts
#	apps/ui/package.json
#	apps/ui/src/components/sidebar-layout/index.tsx
#	apps/ui/src/data/core/connectors/hosted/index.ts
#	apps/ui/src/data/core/connectors/ipc/index.ts
#	apps/ui/src/main.hosted.tsx
#	apps/ui/src/ui-classic/components/session-view/index.tsx
#	apps/ui/vite.config.ts
#	package-lock.json
#	tools/common/ai/sessions/placement.ts
#	tools/common/lib/app-bump-stats.ts
#	tools/common/package.json
#	tools/common/sites/blueprint-extract.ts
#	tools/common/sites/create.ts
#	tools/common/sites/edit.ts
#	tools/common/sites/snapshots.ts
#	tools/common/sites/sync.ts
@youknowriad youknowriad changed the title Add studio ui local web surface + converge desktop/local backend (PoC) Add the studio ui local web surface (PoC) Jun 30, 2026
@youknowriad youknowriad marked this pull request as draft June 30, 2026 10:42
Moves apps/cli/lib/run-wp-cli-command.ts (+ test) and the surfaces design doc out of this surface PoC into dedicated follow-ups (#4005, #4006) so this PR is just the studio ui surface.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@youknowriad youknowriad changed the title Add the studio ui local web surface (PoC) Add the studio ui local web surface Jun 30, 2026
@youknowriad youknowriad marked this pull request as ready for review June 30, 2026 11:07
youknowriad and others added 2 commits June 30, 2026 13:17
The collapsed-state floating toggle (sidebar-layout) and the session header's toggle spacer (session-view) reserved the macOS traffic-light gap based on isFullscreen alone, and referenced a CSS class the stylesheet had since renamed (*Fullscreen -> *Flush). In the browser (studio ui / hosted) there are no traffic lights, so the gap was reserved anyway and the renamed class never applied, leaving the toggle/dropdown misaligned. Drive both off useTrafficLightSpace() (the connector capability, false in the browser) and the matching *Flush class, mirroring the expanded sidebar header.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The new apps/local workspace was missing from the lockfile, so npm ci failed the package.json/lock sync check on CI. Registers @studio/local (its external deps were already resolved).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
youknowriad and others added 3 commits June 30, 2026 13:32
Port the desktop's pure URL builder so 'Create new' in the Publish picker opens the WordPress.com hosted-site checkout instead of silently closing. Also corrects a stale 'out of scope' comment over the snapshot/sync methods, which are fully implemented.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A browser tab can't receive the wp-studio:// deep link the desktop uses to learn the remoteSiteId after the 'Create new' WordPress.com checkout. Instead, when the user opens that checkout, the connector asks the local server to watch: it snapshots the account's syncable sites, polls for the new one (matching the chosen name/domain, else the single new site), and reports it on a new 'sync-connect' SSE channel. The connector's onSyncConnectSite — previously a no-op — now delivers that to the existing auto-connect hook, so no UI listener changes were needed. Watch is keyed by studio site id (re-trigger supersedes), bounded to ~5 min, and torn down on server close.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Gives the local surface a friendlier origin while keeping a secure context: *.localhost is a 'potentially trustworthy' origin, so the clipboard + crypto.subtle APIs the UI uses keep working over plain HTTP (a bare .local host would forfeit that). It resolves to loopback natively on macOS and in Chromium/Firefox, so no hosts entry or cert is needed. The server still binds 127.0.0.1; only the browser-facing URL changes. localhost/127.0.0.1 stay in the CORS allowlist as a fallback, and STUDIO_LOCAL_URL_HOST overrides the host for rollback.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
youknowriad and others added 4 commits June 30, 2026 14:51
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Keep the local surface simple — drop the studio.localhost experiment (and its CORS entries); the server opens http://localhost:8081 as before.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…omains from the connector

The connector's getAllCustomDomains just mapped every site to its customDomain — data the UI already holds via getSites(). Replace it with a useSites()-derived selector in useExistingCustomDomains, removing the method from the Connector interface and all three connectors (ipc/hosted/local). Net deletion, and it closes the local-surface gap for free (the local connector returned [] only because there was no route). The desktop IPC handler stays — the legacy apps/studio renderer still calls it directly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ledSite from the connector

Like getAllCustomDomains, the desktop handler was just sites.find(s => s.enableXdebug) — data the UI already holds via getSites(). useXdebugEnabledSite now derives from useSites(); the method is removed from the Connector interface and all three connectors. Closes the local-surface gap (it returned null only for lack of a route). The desktop IPC handler stays — the legacy apps/studio renderer still calls it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…clid-7036cd

# Conflicts:
#	apps/ui/src/data/core/connectors/hosted/index.ts
…clid-7036cd

# Conflicts:
#	apps/cli/package.json
#	apps/cli/tsconfig.json
#	apps/cli/vite.config.base.ts
#	package-lock.json
@youknowriad

Copy link
Copy Markdown
Contributor Author

@sejas @gcsecsey @bcotrim I'd love some quick testing for this PR if anyone have time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants