Skip to content

feat(ide)!: rebuild IDE on Smart Cache primitives with WordPress components and CodeMirror 6#3784

Merged
jasonbahl merged 645 commits into
wp-graphql:mainfrom
josephfusco:feat/ide-rebuild-pr
Jun 9, 2026
Merged

feat(ide)!: rebuild IDE on Smart Cache primitives with WordPress components and CodeMirror 6#3784
jasonbahl merged 645 commits into
wp-graphql:mainfrom
josephfusco:feat/ide-rebuild-pr

Conversation

@josephfusco

@josephfusco josephfusco commented Apr 20, 2026

Copy link
Copy Markdown
Member

Summary

Rebuilds the WPGraphQL IDE as a React + CodeMirror 6 client on @wordpress/data + @wordpress/components, and moves saved-document storage onto Smart Cache's graphql_document post type.

Architecture

  • 11 PSR-4 PHP classes under includes/: PostTypes, UserMeta, Access, GraphQLSchema, Telemetry, ImportExport, Rest, AssetEnqueue, SettingsPage, AdminUI, SmartCacheBridge.
  • 10 @wordpress/data stores: app, activity-bar, document-editor, response-extensions, editor-bottom-tabs, status-bar-items, response-view-modes, response-actions, editor-actions, document-tab-actions.
  • Saved documents now live in Smart Cache. The IDE's graphql_ide_query post type and graphql_ide_collection taxonomy are removed; SmartCacheBridge filters Smart Cache's graphql_document + 4 doc taxonomies into REST and adds _graphql_ide_variables / _graphql_ide_headers meta + matching GraphQL fields for execution context.
  • Progressive enhancement. Saved-doc features gate on a hasSmartCache bootstrap flag. Without Smart Cache the IDE works standalone with local-only unsaved tabs.
  • IDE-owned CPT: graphql_ide_history.
  • User meta for cross-device prefs; localStorage for ephemeral UI state.

Three render modes

  1. Standalone at /wp-admin/admin.php?page=graphql-ide
  2. Drawer from the admin bar — wp-admin and front-end
  3. Endpoint mode at the GraphQL endpoint URL with Accept: text/html (opt-in setting)

All three are e2e covered, with an autocomplete-above-drawer regression guard.

Data layer

Document, collection, and history CRUD run through WPGraphQL via src/api/graphql-client.js. REST remains for user-preference writes, the aggregated documentSettings readback field, and import/export/reorder.

Extension API

JS (window.WPGraphQLIDE): registerPreference, registerActivityBarPanel, registerWorkspaceTabType + openWorkspaceTab, registerTopbarAction, registerDocumentEditorToolbarButton, registerResponseExtensionTab, registerEditorBottomTab, registerStatusBarItem, registerResponseViewMode, registerResponseAction, registerEditorAction, registerDocumentTabAction.

Execution hooks: executeRequest / executeResponse filters; wpgraphql-ide.afterExecute action.

Canonical reference: plugins/wp-graphql-ide/API_SURFACE.md.

Features

  • Multi-document tabbed editor with auto-save and +N overflow
  • Smart Cache validates the AST and writes the sha256 slug on save; published docs are immutable
  • Execution history with HTTP method tracking
  • Collections (drag-to-assign, collapsible)
  • Docs Explorer with field search and cmd+click navigation from editor
  • Settings workspace tab (autosaves)
  • Tracing tab: verdict block, inline duration bars, N+1 detection, click-to-jump-to-cursor
  • Keyboard shortcuts: Cmd+Enter execute, Ctrl+Shift+P prettify, arrows between tabs

Internationalization

Every UI string goes through @wordpress/i18n with the wpgraphql-ide text domain — chrome, tabs, dialogs, panels, settings, notices, registry labels.

Security

  • REST routes require manage_graphql_ide; GraphQL connections + node lookups scoped to the current user
  • Single-document / history responses restricted to the author
  • Document title length capped on every write path
  • wpgraphql_ide_capability_required filter is now consulted at every IDE permission check, not just admin-menu render
  • Settings descriptions sanitized via wp_kses_post()

Accessibility (WCAG AA)

  • Keyboard navigation through all interactive elements
  • aria-pressed / aria-expanded / aria-label throughout
  • Focus-visible states on all controls

Issues addressed

Test plan

  • 277 unit tests across 26 suites
  • Playwright e2e for all three render modes
  • PHPStan level 8
  • PHPCS (WordPress Coding Standards)
  • Build clean
  • CI matrix: WP 6.1 / 6.2 / 6.5 / 6.8 / 6.9 / trunk × PHP 7.4–8.4 × twentytwentyone / twentytwentyfive × single + multisite

@vercel

vercel Bot commented Apr 20, 2026

Copy link
Copy Markdown

@josephfusco is attempting to deploy a commit to the WPGraphql Team on Vercel.

A member of the Team first needs to authorize it.

@josephfusco josephfusco marked this pull request as draft April 20, 2026 18:52
@codecov

codecov Bot commented Apr 20, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 84.6%. Comparing base (7a9d448) to head (c27fb84).
⚠️ Report is 3 commits behind head on main.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff            @@
##              main   #3784     +/-   ##
=========================================
+ Coverage     83.5%   84.6%   +1.1%     
+ Complexity    5284    4362    -922     
=========================================
  Files          286     221     -65     
  Lines        22753   19446   -3307     
=========================================
- Hits         18995   16446   -2549     
+ Misses        3758    3000    -758     
Flag Coverage Δ
wp-graphql-acf-wpunit-twentytwentyfive-single ?
wp-graphql-wpunit-twentytwentyfive-multisite 84.5% <ø> (ø)
wp-graphql-wpunit-twentytwentyfive-single 84.5% <ø> (ø)
wp-graphql-wpunit-twentytwentyone-multisite 84.5% <ø> (ø)
wp-graphql-wpunit-twentytwentyone-single 84.5% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.
see 65 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR begins a ground-up rebuild of the WPGraphQL IDE UI: moving off GraphiQL’s UI components, adopting WordPress admin components, and replacing the editors with CodeMirror 6 while extending the app store for execution-related state.

Changes:

  • Replace GraphiQL-based UI/layout with a new IDELayout built on @wordpress/components and CodeMirror 6 editors.
  • Add new app-store state for variables/headers/response/isFetching plus hooks for schema introspection and execution.
  • Update docs, build/enqueue assets, and add/adjust unit tests + Jest transforms for CM6 dependencies.

Reviewed changes

Copilot reviewed 38 out of 39 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
plugins/wp-graphql-ide/wpgraphql-ide.php Adjusts enqueued render CSS asset name/version; removes GraphiQL-specific admin notice padding.
plugins/wp-graphql-ide/tests/unit/specs/hooks/useSchema.test.js Adds unit coverage for the new useSchema hook.
plugins/wp-graphql-ide/tests/unit/specs/hooks/useExecution.test.js Adds unit coverage for the new useExecution hook.
plugins/wp-graphql-ide/tests/unit/specs/editors/ResponseViewer.test.js Adds tests for the new CM6-based response viewer.
plugins/wp-graphql-ide/tests/unit/specs/editors/JSONEditor.test.js Adds tests for the new CM6-based JSON editor.
plugins/wp-graphql-ide/tests/unit/specs/editors/GraphQLEditor.test.js Adds tests for the new CM6-based GraphQL editor.
plugins/wp-graphql-ide/tests/unit/jest.config.js Updates Jest transforms to compile CM6/GraphQL LS dependencies in node_modules.
plugins/wp-graphql-ide/styles/wpgraphql-ide.css Removes GraphiQL/CM5-specific styling.
plugins/wp-graphql-ide/src/stores/app/app-store-selectors.js Adds selectors for variables/headers/response/isFetching.
plugins/wp-graphql-ide/src/stores/app/app-store-reducer.js Extends initial state + reducer cases for new execution-related fields.
plugins/wp-graphql-ide/src/stores/app/app-store-actions.js Adds actions for variables/headers/response/isFetching updates.
plugins/wp-graphql-ide/src/stores/activity-bar/activity-bar-selectors.js Adds visiblePanel selector for the new activity panel rendering flow.
plugins/wp-graphql-ide/src/registry/editor-toolbar-buttons/prettify-button.js Replaces GraphiQL icon usage with WordPress icons.
plugins/wp-graphql-ide/src/registry/editor-toolbar-buttons/merge-fragments-button.js Replaces GraphiQL icon usage with WordPress icons.
plugins/wp-graphql-ide/src/registry/editor-toolbar-buttons/copy-query-button.js Replaces GraphiQL icon usage with WordPress icons and renames clipboard import.
plugins/wp-graphql-ide/src/hooks/useSchema.js Introduces schema introspection hook wired to the app store.
plugins/wp-graphql-ide/src/hooks/useExecution.js Introduces execution hook (variables/headers parsing, abort support, response formatting).
plugins/wp-graphql-ide/src/components/ide-layout.css Adds WordPress-admin-styled layout and CodeMirror sizing styles.
plugins/wp-graphql-ide/src/components/editors/index.js Exports new editor components.
plugins/wp-graphql-ide/src/components/editors/ResponseViewer.jsx Adds CM6 read-only JSON response viewer.
plugins/wp-graphql-ide/src/components/editors/JSONEditor.jsx Adds CM6 JSON editor for variables/headers.
plugins/wp-graphql-ide/src/components/editors/GraphQLEditor.jsx Adds CM6 GraphQL editor with cm6-graphql schema integration.
plugins/wp-graphql-ide/src/components/ShortKeysDialog.jsx Migrates from GraphiQL Dialog to WP Modal and updates copy/markup.
plugins/wp-graphql-ide/src/components/SettingsDialog.jsx Migrates from GraphiQL Dialog to WP Modal/controls (Theme + settings UI).
plugins/wp-graphql-ide/src/components/IDELayout.jsx New main IDE composition using WP components, editors, and new hooks.
plugins/wp-graphql-ide/src/components/GraphiQL.jsx Removes the prior GraphiQL provider/interface implementation.
plugins/wp-graphql-ide/src/components/EditorToolbar.jsx Replaces GraphiQL toolbar button UI with WP Button + Tooltip.
plugins/wp-graphql-ide/src/components/EditorGroup.jsx Removes the prior GraphiQL editor group layout.
plugins/wp-graphql-ide/src/components/AppDrawer.jsx Updates focus targeting from GraphiQL/CM5 elements to CM6.
plugins/wp-graphql-ide/src/components/App.jsx Switches from GraphiQL wrapper to IDELayout; extends fetcher to accept headers + abort signal.
plugins/wp-graphql-ide/src/components/ActivityPanel.jsx Refactors activity panel rendering to use store-driven visible panel and WP ResizableBox.
plugins/wp-graphql-ide/src/components/ActivityBarUtilities.jsx Migrates activity bar utilities UI to WP Buttons/Tooltips/icons.
plugins/wp-graphql-ide/src/components/ActivityBarPanels.jsx Migrates activity bar panel buttons to WP components and store-driven toggle action.
plugins/wp-graphql-ide/src/components/ActivityBar.jsx Updates activity bar composition to match new panel/utilities components.
plugins/wp-graphql-ide/package.json Renames package to @wpgraphql/ide, updates deps (CM6, cm6-graphql), adjusts scripts.
plugins/wp-graphql-ide/CONTRIBUTING.md Updates workspace commands to the new package name.
plugins/wp-graphql-ide/ACTIONS_AND_FILTERS.md Updates docs to reflect renamed/non-GraphiQL hooks and removes legacy section.
package-lock.json Updates lockfile for new dependencies and package rename.
CLAUDE.md Updates workspace commands to the new package name.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread plugins/wp-graphql-ide/src/components/IDELayout.jsx Outdated
Comment thread plugins/wp-graphql-ide/src/hooks/useExecution.js Outdated
Comment thread plugins/wp-graphql-ide/src/components/ActivityPanel.jsx
Comment thread plugins/wp-graphql-ide/src/components/ActivityBarUtilities.jsx Outdated
Comment thread plugins/wp-graphql-ide/tests/unit/specs/hooks/useExecution.test.js Outdated
Comment thread plugins/wp-graphql-ide/package.json Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 59 out of 62 changed files in this pull request and generated 11 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread plugins/wp-graphql-ide/src/components/IDELayout.jsx Outdated
Comment thread plugins/wp-graphql-ide/src/hooks/useExecution.js Outdated
Comment thread plugins/wp-graphql-ide/src/components/IDELayout.jsx Outdated
Comment thread plugins/wp-graphql-ide/src/components/IDELayout.jsx Outdated
Comment thread plugins/wp-graphql-ide/src/components/IDELayout.jsx Outdated
Comment thread plugins/wp-graphql-ide/tests/unit/specs/hooks/useSchema.test.js Outdated
Comment thread plugins/wp-graphql-ide/package.json Outdated
Comment thread plugins/wp-graphql-ide/src/hooks/useSchema.js Outdated
Comment thread plugins/wp-graphql-ide/src/hooks/useSchema.js
Comment thread plugins/wp-graphql-ide/src/components/IDELayout.jsx Outdated
@josephfusco

This comment was marked as outdated.

@josephfusco josephfusco force-pushed the feat/ide-rebuild-pr branch 2 times, most recently from ff8a28f to e84b452 Compare April 23, 2026 19:09
@github-project-automation github-project-automation Bot moved this to 🆕 Backlog in Headless OSS Apr 27, 2026
@josephfusco josephfusco moved this from 🆕 Backlog to Community In Review in Headless OSS Apr 27, 2026
@josephfusco josephfusco force-pushed the feat/ide-rebuild-pr branch from eb3844c to 84bc463 Compare May 3, 2026 00:59
josephfusco added a commit to josephfusco/wp-graphql that referenced this pull request May 5, 2026
CI lint failure on PR wp-graphql#3784: PHPStan crashed with
`PathNotFoundException: ../wp-graphql/vendor/webonyx/graphql-php/src
does not exist`. The path I added earlier resolved locally because
the WPGraphQL monorepo has both plugins side by side with their vendor
dirs populated, but each plugin's lint job only installs its own
composer dependencies — wp-graphql's vendor isn't there.

Switch to the stubs package this plugin already requires for static
analysis (axepress/wp-graphql-stubs). It contains class definitions
for GraphQL\Language\Parser, GraphQL\Language\Printer, and
GraphQL\Error\SyntaxError, so PHPStan resolves them without touching
the sibling plugin. The stubs aren't auto-loaded (the package ships
no extension.neon), so the path is registered explicitly via
`scanFiles`.

Verified locally: composer phpstan exits 0 errors.
josephfusco added a commit to josephfusco/wp-graphql that referenced this pull request May 5, 2026
…n the dep

PR wp-graphql#3784 CI flagged the @graphiql/react import in
GraphiQLToolbar.js as unresolved. The package resolves at runtime via
graphiql@1.x's nested node_modules, just not at a path the lint
resolver walks.

We considered adding @graphiql/react as an explicit top-level dep but
backed off — that file lives in the legacy `wpgraphiql/` package that
wp-graphql-ide is replacing. Adding a permanent dep extends the
legacy IDE's life with a new contract; an eslint-disable on the one
import keeps lint quiet without committing to maintain the package.

If/when the legacy `wpgraphiql/` package is removed in a follow-up,
this comment goes with it.
josephfusco added a commit to josephfusco/wp-graphql that referenced this pull request May 5, 2026
PR wp-graphql#3784 CI flagged the @graphiql/react import in
GraphiQLToolbar.js as unresolved. The package resolves at runtime
through graphiql@1.x's nested node_modules, just not at a path the
lint resolver walks.

Both imported hooks (usePrettifyEditors, useHistoryContext) drive
real button behavior on this toolbar, so the import has to stay.
Adding @graphiql/react as an explicit top-level dep would silence
the lint, but it'd also commit the repo to maintaining a version of
an upstream package we don't otherwise consume. The
`eslint-disable-next-line` is the smaller change.
@josephfusco josephfusco force-pushed the feat/ide-rebuild-pr branch from 493f112 to 4394203 Compare May 5, 2026 20:08
…le in SPL autoloader

CI's PHPCS run treats warnings as errors, so the variable-path
require_once inside the autoloader fallback (added in bb946a5)
failed Schema Linter and any check-cs report-mode workflow that runs
with --report-checkstyle.

The VIP sniff is a false positive here — the autoloader's whole job
is to resolve a class name to a path at runtime, and the value is
already constrained by:
- prefix check (must start with WPGraphQLIDE\)
- file_exists() guard before include

Same justification AssetEnqueue.php uses for its build-asset includes.
@josephfusco josephfusco requested a review from Copilot May 9, 2026 18:22

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review this pull request because it exceeds the maximum number of lines (20,000). Try reducing the number of changed lines and requesting a review from Copilot again.

Three e2e specs were locked to the pre-refactor behavior of temp
drafts:

- A temp tab "shows the unsaved-dirty indicator"
- After Escape from SaveDialog, asserted the dirty bullet was visible
- "Save draft button is disabled when the doc has no unsaved changes"
  pointed at a fresh temp tab

The current contract (autopersisted temps + Save promotes temp →
draft):
- Temp tabs are marked with `is-temp` (italic title) — not the dirty
  bullet, which would always be on for them and become useless.
- Save stays enabled on temp tabs even when activeDocDirty is false,
  because clicking it is the user's path to promoting a temp.

Updated each spec to assert the new contract. The third test's
intent — "Save reflects the underlying contract" — survives, just
inverted: temp tabs keep the button enabled.
@josephfusco

Copy link
Copy Markdown
Member Author
CleanShot 2026-05-09 at 14 28 06@2x

The execute button was 32×32 with a 16px icon — tight enough that
hitting it took deliberate aim, even with a mouse. Bump to 44×44
(WCAG 2.5.5 AAA minimum) with a 20px icon. The button reads as
primary at the new size; the rest of the execution pill keeps its
existing scale so the play button visually anchors the row.
josephfusco and others added 13 commits June 4, 2026 11:17
Sync main (split PRs wp-graphql#3877/wp-graphql#3878/wp-graphql#3879 + asset pipeline wp-graphql#3880/wp-graphql#3882/wp-graphql#3883) onto
the IDE rebuild branch, on top of Joe's floor/CI commits (floor 6.1, WP 7.0 CI
coverage + tested-up-to). Conflicts (all IDE release metadata) resolved as:

- Version / Stable tag / package.json: 4.5.0 (release-please manifest value;
  release-please bumps to 5.0.0 at release — in-repo version tracks last release
  per the plugin's CLAUDE.md).
- CHANGELOG.md / readme.txt changelog: union — keep the 5.0.0 notes AND main's
  released 4.5.0 entry. Dropped the now-inaccurate "raised floor to 7.0"
  breaking-change note (Joe set the floor to 6.1) and the 7.0-floor framing in
  the i18n docs.
- Requires at least: 6.1 / Tested up to: 7.0 — kept Joe's values.
The IDE's `codeception.dist.yml` boots Smart Cache via WPLoader's
`plugins:` list. Smart Cache's entry file only loads its classes when
its own `vendor/autoload.php` is present:

  if ( file_exists( __DIR__ . '/vendor/autoload.php' ) ) {
      require __DIR__ . '/vendor/autoload.php';
  }

The reusable workflow ran `composer install` for the plugin under test
and (conditionally) for `plugins/wp-graphql`, but never for
`plugins/wp-graphql-smart-cache`. So inside the test container:

  1. Smart Cache's `Document` class never autoloaded.
  2. `can_load_plugin()` returned false (Document::class missing).
  3. Smart Cache's `init` callback bailed -> `graphql_document` post
     type was never registered.
  4. `SmartCacheBridge::register()` also bailed on the same
     `class_exists` guard, so `show_in_rest` was never filtered on.
  5. Every `/wp/v2/graphql_document/*` REST test 404'd; every
     `graphqlDocument` GraphQL query returned `{errors: ...}` with no
     `data` key (17 failures + 9 errors per matrix row).

The CI band-aids on the branch (re-activating the IDE plugin against
`tests-cli` before the WPUnit suite) were not the fix — WPLoader
bootstraps its own in-process WordPress and doesn't read the
`tests-cli` container's `active_plugins` option.

Mirror the existing `install_wp_graphql_core_deps` pattern: add an
`install_wp_graphql_smart_cache_deps` input to the reusable workflow,
add a conditional composer-install step for Smart Cache, and pass
`true` from the IDE caller.
Same root cause as 877b5c7, but in the JS E2E reusable workflow.

`.wp-env.json` mounts wp-graphql-smart-cache for every plugin's test
container, and Smart Cache's entry file only loads its classes when
its own vendor/autoload.php is present. The E2E reusable workflow
already follows this pattern for wp-graphql core (auto-install for any
plugin that isn't wp-graphql itself) — extend it to Smart Cache.

Without this, Smart Cache's post types + GraphQL types
(`graphql_document`, `graphqlDocumentGroups`, …) never register and
the IDE's e2e suite fails with `Cannot query field "graphqlDocumentGroups"
on type "RootQuery"` on first render.
The endpoint-mode e2e suite toggled the public-endpoint setting with:

  wp option patch update graphql_ide_settings graphql_ide_public_endpoint $value
  || wp option patch insert graphql_ide_settings graphql_ide_public_endpoint $value

`wp option patch` requires the option to exist as a serialized array.
On a fresh tests-cli container the option is missing (or coerced to an
empty string by WordPress internals when no settings have been saved),
so `patch insert` errors with:

  Cannot create key "graphql_ide_public_endpoint" on data type string

`wp option patch update` then errors with "No data exists for key"
because the array doesn't have the subkey yet, and the fallback `||`
hits the same string-coercion wall.

Just overwrite the whole `graphql_ide_settings` option with the
desired state via `wp option update --format=json`. The other IDE
settings are read through `get_graphql_setting()` which defaults a
non-array section to `[]` and falls back to each field's declared
default, so absence == declared default for any field not in the
payload. `afterAll` resets `graphql_ide_public_endpoint` to "off",
which is also its declared default.
@josephfusco

Copy link
Copy Markdown
Member Author
CleanShot 2026-06-04 at 14 52 37@2x

The Playground Preview workflow was failing on every fork PR at the
sticky-comment step with "Resource not accessible by integration"
because `pull_request` events from forks always get GITHUB_TOKEN
downgraded to read-only, regardless of the `permissions:` block.

Splitting the workflow is the standard fix:

  playground-preview.yml (pull_request, no write perms)
    Builds the plugin ZIP and uploads it.
    Uploads a small pr-meta artifact with the PR number + head SHA.

  playground-preview-comment.yml (workflow_run, pull-requests: write)
    Triggered when Playground Preview finishes successfully.
    Downloads the pr-meta artifact via the cross-run download mode of
    actions/download-artifact@v4, builds the Playground URL using the
    triggering workflow's run_id (so the existing nightly.link route
    still resolves), and posts/updates the sticky comment.

The comment workflow runs in the base repo context with the base repo's
GITHUB_TOKEN, so it can comment on PRs from forks. The build workflow
no longer requests pull-requests:write — fork PRs would have it
silently dropped anyway, and decoupling makes the intent explicit.

Standard workflow_run caveat: GitHub always executes the copy of the
trigger file on the default branch, so the comment workflow won't
take effect on this PR. It'll start working for fork PRs once this
merges. The `preview` check goes green here regardless because the
failing step is gone.
Two changes:

1. Write the assembled Playground URL to `$GITHUB_STEP_SUMMARY` from
   the build workflow. This makes the link reachable on the workflow
   run page for every PR — including fork PRs and including this PR
   itself — without depending on the workflow_run-triggered sticky
   comment landing first. Step summaries don't need any special token
   permissions, which is the whole point.

   Follows the WordPress Playground "Open in Playground" badge
   convention via a shields.io badge → playground.wordpress.net#<blueprint>
   URL, so reviewers see a button that matches the rest of the
   ecosystem (Gutenberg PRs, core Playground demos).

2. Compute the Playground URL once in the build workflow and stash it
   in the pr-meta artifact alongside PR_NUMBER and HEAD_SHA. The
   comment workflow now reads the URL instead of rebuilding the same
   base64 blueprint with the same heredoc. Single source of truth,
   and a blueprint change touches one file.
`wp-scripts plugin-zip` derives the output filename from package.json's
`name` field and adds discovered files flat at the archive root. The
IDE's workspace name `@wpgraphql/wp-graphql-ide` makes that produce:

  - filename `@wpgraphql/wp-graphql-ide.zip` (with `@wpgraphql/` as a
    literal subdirectory of the build cwd)
  - contents flat at root (no top-level wrapping directory)

Both of those are wrong for any consumer that follows the WP plugin
convention. WordPress, wp.org's installer, and WordPress Playground's
`installPlugin` step all expect a ZIP containing a single top-level
directory whose name equals the plugin slug. Without it, Playground's
extraction lands files in a derived location that doesn't match
`wp-content/plugins/wp-graphql-ide`, so the subsequent `activatePlugin`
step fails with "wasn't able to find the plugin
/wordpress/wp-content/plugins/wp-graphql-ide".

Replace `build:zip` with `bin/build-plugin-zip.js`, which reuses the
same file discovery (`npm-packlist`, honoring the `files` field) but
writes everything under a `wp-graphql-ide/` prefix to a flat
`wp-graphql-ide.zip` at the workspace root. `adm-zip` and
`npm-packlist` are already transitively available via
`@wordpress/scripts`, so no new dependencies.

The Playground preview workflow can now skip its "Normalize ZIP
filename" find/mv hack — the build script writes the right artifact
in the right place directly.

Also: add `wp-graphql-ide.zip` (with the dash matching the plugin
slug) to the IDE's .gitignore; the existing entry was for the old
slug-less filename `wpgraphql-ide.zip`.
`actions/upload-artifact@v4` always wraps whatever it's given in a
ZIP — that wrapper is the file the user (or Playground, via
nightly.link) downloads. Uploading our already-built
`wp-graphql-ide.zip` as a single file therefore produced a
double-zipped download: extracting one level revealed the inner ZIP
file at the root, not the plugin directory. Playground's
`installPlugin` step extracts exactly one level, found a `.zip`
sitting where a plugin folder should be, and the subsequent
`activatePlugin` failed with "wasn't able to find the plugin
/wordpress/wp-content/plugins/wp-graphql-ide".

Extract the properly-shaped ZIP into a staging dir and upload the
extracted contents instead. GitHub's artifact wrapper becomes the
one and only ZIP layer, the download contains `wp-graphql-ide/<files>`
at its root, and Playground's installer is happy.

`build:zip` itself still produces `wp-graphql-ide.zip` for wp.org /
manual distribution — only the Playground workflow's artifact path
changes.
…it up

`actions/upload-artifact@v4` defaults `include-hidden-files` to false
and skips paths whose names start with `.`, so the previous staging
dir `.playground-artifact/` produced "No files were found with the
provided path" and uploaded nothing. Drop the dot from both the
workflow path and the .gitignore entry — staging is meant to be
visible to the upload action.
The new bin script written in 7f2b6d9 used spaces inside parens /
braces (the @wordpress/scripts-style I'd written from muscle memory),
which the IDE's Prettier config rejects. 16 auto-fixable formatting
errors, no behavior change. Ran `lint:js:fix` to align with the
rest of the workspace.
The `files` field in `package.json` listed `build`, `styles`,
`plugins/*/*.php`, etc. but omitted `includes` — so every PSR-4
class file and every modular helper that `wpgraphql-ide.php`
require_once's was missing from the published archive. The very
first line of plugin bootstrap is

  require_once __DIR__ . '/includes/access-functions.php';

which fataled with no output, surfacing in WordPress Playground as

  PHP.run() failed with exit code 255
  === Stdout === (empty)
  === Stderr === (empty)

(Empty streams because PHP died before any output buffering ran.)

Add `includes` to the files list. Verified locally: rebuilt ZIP now
contains 19 PHP files from `includes/` (access-functions.php,
Access.php, AdminUI.php, AssetEnqueue.php, SmartCacheBridge.php,
the document-settings/ subtree, etc.) where it previously had zero.

The same gap would have broken wp.org installs the moment users
tried to download the published ZIP. Bug pre-dates this branch — the
files list never had it.
Regression guard for the bug fixed in ad9a664. The IDE's
\`package.json\` \`files\` list and the entry file's \`require_once
__DIR__ . '/...'\` graph have to stay in sync — when they don't, the
published ZIP is missing files the plugin needs at load time and the
install fatals before any output buffer flushes (which is why
Playground saw \`PHP.run() failed with exit code 255\` with empty
stdout AND empty stderr).

The test:
  - Rebuilds wp-graphql-ide.zip via bin/build-plugin-zip.js so the
    assertion is over what would actually ship, not a stale artifact.
  - Asserts the archive has a single \`wp-graphql-ide/\` top-level
    directory (Playground / WP / wp.org all require it).
  - Parses every \`require_once __DIR__ . '/path.php'\` literal out of
    wpgraphql-ide.php and asserts each target is in the archive.
    Adding a new require automatically extends coverage — no test
    edits needed.
  - Filters the one conditional require (\`vendor/autoload.php\`,
    intentionally optional; the SPL fallback covers it).
  - Asserts the four webpack entry points the IDE enqueues at
    runtime (.js + .asset.php for both main and render bundles).

Wired into \`.github/workflows/playground-preview.yml\` as a step
after build so every fork PR runs it — Jest isn't otherwise in CI
yet, and running this single spec adds <1s.

Lives at \`tests/unit/specs/distribution/\` rather than \`.../build/\`
because the plugin's \`.gitignore\` has a top-level \`build/\` entry
(for the webpack output dir) that would otherwise swallow this
file too.
@josephfusco

Copy link
Copy Markdown
Member Author

Here is the WordPress Playground for this pull request, as of this run.

This is a one-off manual comment because the auto-comment workflow ships in this PR, as it cannot post here yet. Once merged, every future PR will get its own auto-updating sticky comment with a fresh Playground link.

Two issues from CI's Lint job on 2d52f88:

1. `import/no-extraneous-dependencies` on \`require('adm-zip')\`.
   Both the new test and bin/build-plugin-zip.js were resolving
   adm-zip transitively through @wordpress/scripts. Declare it as
   a direct devDep so the dependency is explicit and ESLint stops
   complaining. Same applies to npm-packlist transitively, but
   that one's only used by bin/* which the lint config already
   exempts.

2. \`prettier/prettier\` on the multi-line template literal in
   the webpack-entrypoint assertion. Autofixed.

Test still passes locally (5/5). Net: package.json gains one line,
package-lock.json gets the matching adm-zip entry, test file gets
the single-line template fixup.
@jasonbahl jasonbahl merged commit 35070c8 into wp-graphql:main Jun 9, 2026
60 of 66 checks passed
@github-project-automation github-project-automation Bot moved this from Community In Review to ✅ Closed in Headless OSS Jun 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants