Add the studio ui local web surface#3953
Open
youknowriad wants to merge 19 commits into
Open
Conversation
…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>
…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>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Collaborator
📊 Performance Test ResultsComparing 73db67f vs trunk app-size
site-editor
site-startup
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>
This was referenced Jun 25, 2026
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>
This was referenced Jun 29, 2026
…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
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>
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>
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>
1 task
…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
Contributor
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Related issues
Builds on the desktop/local backend convergence that landed separately — the
@studio/commonextractions 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/commonmodules (extracted and reviewed in separate PRs, now on trunk); this PR assembles thestudio uisurface 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 uistarts a small local web server (apps/local) that serves the existingapps/uiapp 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/commonbackend 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 uicommand 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 typecheckis clean fortools/common,apps/studio,apps/cli,apps/ui,apps/local.npm run build:local --workspace=apps/uiproducesdist-local.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
typecheckclean across all workspaces anddist-localbuilds; runtime console + visual still need a human pass per the note above.🤖 Generated with Claude Code