Studio Panels: agent-generated wp-admin UI in the preview pane#3345
Draft
youknowriad wants to merge 21 commits intotrunkfrom
Draft
Studio Panels: agent-generated wp-admin UI in the preview pane#3345youknowriad wants to merge 21 commits intotrunkfrom
youknowriad wants to merge 21 commits intotrunkfrom
Conversation
Initial wp-build-based plugin shell that will host agent-driven wp-admin panels. Wires the new workspace into root scripts and lint. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
list/ renders a parameterized DataView for any post type, fed by core-data's getEntityRecords with full URL-driven filter/page/search. scratch/ is the slot studio_generate_panel overwrites with on-the-fly TSX. Both follow wp-build's routes/ convention so the generated page auto-discovers them. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- studio_show_panel and studio_generate_panel agent tools (one file each under apps/cli/ai/tools/), wired into the preview-steering set so they only register when Studio desktop is on the other end of the IPC - studio-panels-installer copies the bundled plugin into a site by version comparison; studio-panels-builder writes agent TSX into the scratch route and runs wp-build for on-the-fly generation - Both tools auto-start the site, install Gutenberg if missing, activate the panels plugin, navigate the preview through /studio-auto-login, and disconnect the daemon bus so the agent child can exit Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
viteStaticCopy now copies apps/studio-panels/{studio-panels.php, version.txt,
build/} into dist/cli/studio-panels/ for all three CLI build configs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… panels - Track sitePath and panelPath independently; agent navigation events route to one or the other based on URL pattern (isPanelPath helper). - Header gains a small Site/Panel segmented toggle. Panel button is disabled until the agent has actually generated a panel. - WebviewSurface gains an enableInspector prop (default true). On panel pages it's false, so the annotate button never appears — those pages are agent-rendered UI, not site content to comment on. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…st stage The wp-build route loader pattern requires useLoaderData() to read the data in the stage, but @wordpress/route 0.5.0 (the shipping npm version) doesn't export that hook. Drop the loader and read URL search params directly with useSearch() — same effect, fewer indirection layers, works with the route package version Gutenberg currently ships. Bumps panels to v0.2.3. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The model needs to see these alongside preview_navigate / preview_reload to choose them for "show me posts" requests. Without the listing it falls back to wp_cli post list (whose own description includes "user list" as an example, which the model generalises). Two neutral one-line entries, no defensive language. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Trunk landed two changes that touched the same areas:
- apps/cli/ai/tools.ts split into apps/cli/ai/tools/*.ts (one tool per
file). My show-panel/generate-panel files slot in cleanly. Dropped my
redundant helpers.ts in favour of trunk's shared utils.ts. Registered
the panel tools in the previewSteeringToolDefinitions array.
- apps/ui SessionUIProvider/useSessionPreviewUI/useSessionCommands
reducer that hoisted preview state out of SitePreview. Reworked my
Site/Panel toggle and panel-path detection to fit:
- isPanelPath now lives in use-session-ui (single source of truth)
- PreviewUIState gains mode + sitePath/panelPath
- preview/navigate routes paths into the right slot via isPanelPath
- SitePreview accepts mode/setMode/hasPanel as props (passed by
session-view from useSessionPreviewUI)
- enableInspector on WebviewSurface derives from current path
Also adopted trunk's `npm run lint --workspaces --if-present` pattern;
@studio/panels gets its own `lint` script for routes/.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
SitePreview is only ever rendered inside SessionUIProvider, so the controlled-component motivation for prop-drilling path/reloadNonce/mode/ setMode/hasPanel doesn't apply. Reading from useSessionPreviewUI() directly drops six props from the call site and matches the convention useConnector / useSession / useSites already use elsewhere in the file. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…el toggle Previously the single SitePreview webview re-navigated on every toggle — including running through /studio-auto-login again — which lost in-page state (scroll position, DataView filters) and felt sluggish. Restructure SessionUI's preview state into two slots (`site` and `panel`, each with its own path + reloadNonce). SitePreview renders one webview per slot, kept mounted side-by-side; CSS visibility:hidden swaps which one is shown. The agent's preview/navigate action only bumps the slot it targets — the other side keeps its previous URL and DOM untouched, so toggling back is instant and the in-page state is intact. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Studio preview pane has its own chrome (window controls, mode toggle, external-link button); the WP admin bar at the top of every wp-admin and logged-in front-end page is visual noise. Inject a small stylesheet via the webview's insertCSS() on each dom-ready so it covers both the site slot and the panel slot, and survives navigation within either. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
WP reserves admin-bar height via !important rules in two media queries (46px at <=782px and a separate <=600px block) on top of the desktop 32px rule. The single top-level `html` rule could be outranked by the later mobile rule depending on cascade order, leaving a visible gap on narrow widths. Override the same selectors inside both media queries so the preview is edge-to-edge regardless of width. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The wp-admin chrome (`#wpwrap`, `#wpcontent`, `#wpbody`, `#wpbody-content`) adds its own top padding on top of the html/body reservation to leave room for the admin bar. Zeroing only the body left a wrapper-level gap visible on the panel surface. Cover those wrapper IDs at every breakpoint the same way as the body selectors. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CSS injection wasn't winning the cascade against WP's admin-bar height reservation in some configurations (mobile media queries, theme rules with their own !important, body inline styles). Switch to running a small JS snippet via executeJavaScript on every dom-ready: it sets inline `style.setProperty(..., 'important')` on html/body and the four wp-admin wrapper IDs, which beats any external rule unconditionally, and a MutationObserver re-applies the fix if WP re-renders the bar later (e.g. via heartbeat). Also wire Cmd/Ctrl+Shift+I to call `webview.openDevTools()` so the inner page is finally inspectable from inside Studio's preview pane — otherwise debugging anything in the iframe is essentially impossible. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previous CSS / JS reset only touched a known list of wp-admin wrapper IDs. Some surfaces (themes, custom admin pages) reserve admin-bar height on a different element that we don't enumerate, so a gap remained even with the bar hidden. After the known-id pass, walk the leftmost-deepest path from `body` for up to 12 levels and zero margin-top / padding-top / border-top-width on any element whose computed value contributes top space. Logs each zeroed element to the inner page's console so the source can be inspected via Cmd/Ctrl+Shift+I when debugging. Also broadens the MutationObserver to catch style/class attribute changes in case WP toggles admin-bar classes after first paint. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…slots `.boot-layout--single-page .boot-layout__stage` and `.boot-layout__inspector` are the layout primitives @wordpress/boot wraps the studio-panels routes in. They reserve their own admin-bar height via a class-based rule that the previous getElementById pass and the leftmost-walker missed (the walker bails after a few levels and these wrappers sit deeper inside). Add them as explicit selectors so the panel surface lands flush with the preview pane header. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
getToolDisplayName fell back to the raw mcp__studio__… ID for ~13 tools (push/pull/import/export, taxonomist, need-for-speed, rank-me-up, list_connected_remote_sites, open/wait_for_annotations, wpcom_request, plus the new studio_show_panel and studio_generate_panel) and a handful of Claude Code preset tools (AskUserQuestion, WebFetch, WebSearch, NotebookEdit), so the chat UI showed cryptic raw names. Add localized labels for all of them, plus getToolDetail clauses so the panel tools surface their `kind · postType` / `summary` and wpcom_request shows `METHOD path`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The JS-based fix only zeroes elements that exist when it runs at dom-ready. @wordpress/boot mounts `.boot-layout--single-page .boot-layout__stage` (and `__inspector`) asynchronously after that, so the very first paint of the panel page still showed a top gap before the MutationObserver caught up. Two complementary fixes: - Inject a high-specificity stylesheet via insertCSS so the rule is in the cascade before boot's elements arrive — covers them as they render, no observer round-trip needed. - Re-run the JS fix on requestAnimationFrame + 100ms + 500ms timeouts to backstop the observer for boot's typical mount timing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…_panel The parameterized list panel was instant but capped at "title/date/ status/id/type/author" columns and one `kind: list` shape. Generative TSX writes a fresh panel per request — slower (~3-6s vs <1s) but unconstrained: any column set, any layout, mixed sources, edit forms, dashboards. The trade-off is intentional now that on-the-fly works reliably. Removed: - apps/cli/ai/tools/show-panel.ts (and its export from tools/index.ts) - apps/studio-panels/routes/list/ (its DataView is what generate-panel emits anyway) - mcp__studio__studio_show_panel entries in tools/common/ai/tools.ts Added — guidance for the agent so generated panels stay idiomatic: - studio_generate_panel description carries six numbered conventions (DataViews for lists, DataForm for forms, components for dashboards, core-data for I/O, mu-plugin extension for custom REST, @wordpress/* imports only). - A "Generate panels" section in the local-mode system prompt mirrors the same conventions so they're in context for the whole session. Bumps panels to 0.3.0 so the installer re-copies on next tool call, clearing the dead routes/list/ from existing sites. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… header The remaining gap turned out to be `top: 46px` on `.boot-layout--single-page .boot-layout__stage|__inspector` (the rule @wordpress/boot uses to leave room for the mobile admin bar at 46px). killTopSpace only zeroed margin/padding/border so positioning offsets slipped through. Add killTopOffset that detects elements with non-zero computed `top` and absolute/fixed/relative positioning, and zeroes both `top` and resets `height: 100%`. Also extend the static CSS injection to set top/height on the same selectors so it covers elements that arrive after the JS runs. Add a "Inspect" icon button to the preview header (uses @wordpress/icons `code` icon) which calls openDevTools() on the active webview surface. The earlier Cmd+Shift+I shortcut on the host window only fired when the host had focus; clicking into the iframe trapped the keystroke. The button is always reachable. WebviewSurface gains a forwardRef + useImperativeHandle exposing openDevTools so SitePreview can call it on whichever slot is currently visible. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…comments - apps/ui/site-preview: remove the Inspect button + WebviewSurfaceHandle (forwardRef/useImperativeHandle gone), drop the body-walker, console diagnostics, rAF/setTimeout retries, and host keydown handler. Slim down HIDE_ADMIN_BAR_CSS/SCRIPT to the minimal set that beats the cascade. - apps/cli/lib/studio-panels-installer: drop unused getBundledPluginDir / getInstalledPluginDir / pluginDir field, inline the pathExists helper, collapse JSDoc to a one-liner. - apps/cli/lib/studio-panels-builder: shorter comments, simpler readVersion, drop the redundant `searched` constructor arg. - apps/cli/ai/tools/generate-panel: collapse validateScratchSource JSDoc to a one-line WHY comment. - apps/ui/use-session-ui: trim comment cruft to the load-bearing WHY lines. - apps/studio-panels/routes/scratch: revert to the placeholder so the file typechecks; the agent overwrites it on demand. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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
How AI was used in this PR
Built end-to-end with Claude Code: scaffolding the
@studio/panelsworkspace, thestudio_generate_panelagent tool, the wp-build round-trip pipeline, and the SitePreview Site↔Panel toggle. Reviewers should pay particular attention to:apps/studio-panels/scripts/post-build.mjs— it works around a wp-build enqueue gate but is fragile to changes in@wordpress/build.HIDE_ADMIN_BAR_CSS/HIDE_ADMIN_BAR_SCRIPTinapps/ui/src/components/site-preview/index.tsx— these mutate WP-rendered DOM inside the webview to remove the admin bar gap; thetop: 46pxrule on.boot-layout__stagewas the surprising bit.apps/cli/lib/studio-panels-builder.ts— runsnpm run buildagainst the source tree, so it's dev-only by design.Proposed Changes
Adds a generative-UI capability to Studio: the agent can generate custom wp-admin pages on demand from prompts like "Show me a list of posts".
@studio/panelsworkspace package (apps/studio-panels/): a wp-admin plugin built with@wordpress/build's pages routing system. Ships ascratchroute that the agent overwrites.studio_generate_panelagent tool (apps/cli/ai/tools/generate-panel.ts): writes TSX into the scratch route, runs wp-build (~200ms), bumps a-scratch.Nversion suffix, and pushes the rebuilt artifact into the site. The tool description encodes 6 conventions (DataViews for lists, DataForm for forms,@wordpress/core-datafor I/O, mu-plugin extension for custom REST,@wordpress/*imports only).apps/ui/src/components/site-preview/): two<webview>s kept mounted with CSSvisibility: hiddenso toggling preserves each side's in-page state. Driven by aSessionUIProviderreducer withsite/panelslots (apps/ui/src/hooks/use-session-ui.tsx).@wordpress/boot's async layout mounts.wp plugin install gutenberg --activate, activates the panels plugin, navigates the preview through/studio-auto-login, and disconnects the daemon bus so the agent child can exit.Dev-mode only: requires the
apps/studio-panels/source tree on disk and writable. A packaged CLI build does not include it.Testing Instructions
npm install(lockfile changes for the new workspace).npm run cli:build && npm start— boot the desktop app.studio_generate_panel, the preview navigates to the panel, the Site/Panel toggle in the preview header becomes enabled, and the rendered panel uses<DataViews>againstgetEntityRecords..boot-layout__stage.<DataForm>against('root', 'site').Pre-merge Checklist
npm run typecheckpassesnpm test apps/cli/ai/tests/tools.test.tspasses (21/21)npx eslintclean on changed filesnpm run cli:buildsucceeds🤖 Generated with Claude Code