GraphiQL v6#4228
Draft
trevor-scheer wants to merge 60 commits into
Draft
Conversation
## Summary - Swap the vestigial `graphiql-5` reference in `.github/workflows/release.yml` for `graphiql-6` so the changesets-action runs on pushes to the integration branch. - Enter changesets pre-mode with the `alpha` tag so merges aggregate into `6.0.0-alpha.N` prereleases. - Add a changeset that seeds the alpha release line by bumping `graphiql` to v6. No functional change — subsequent alphas accumulate the redesign work. ## Test plan - [ ] On merge: changesets-action opens a "Version Packages (alpha)" PR bumping `graphiql` to `6.0.0-alpha.0`. - [ ] Merging the version PR publishes `graphiql@6.0.0-alpha.0` to npm with the `alpha` dist-tag. Refs: #4219
🦋 Changeset detectedLatest commit: d79f78f The changes in this PR will be included in the next version bump. This PR includes changesets to release 8 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Contributor
|
The latest changes of this PR are not available as canary, since there are no linked |
## Summary - Introduce a new `packages/graphiql-react/src/style/tokens.css` with the v6 OKLCH-based design token system. Both dark and light palettes ship together. - Light theme activates explicitly via `data-theme="light"` or automatically via `prefers-color-scheme: light` when no theme is pinned. Dark remains the default. - Existing v5 HSL variables are unchanged; nothing in `@graphiql/react` references the new tokens yet. - Future PRs will restyle components to consume the new tokens and shim the v5 variables. Refs: #4219
## Summary Storybook gives us a fast feedback loop for iterating on the look of the app and individual components — flipping themes/density/font-size without spinning up the full GraphiQL shell. - Bootstrap Storybook 10 in `@graphiql/react`. Stories colocated as `<component>.stories.tsx`; ships one starter (`Spinner`) to validate the pipeline. - A global decorator wraps every story in `.graphiql-container` with `data-theme` / `data-density` / `data-font-size` attributes, toggleable from the Storybook toolbar. - Move `Uri`, `KeyMod`, `KeyCode`, and `Range` out of the `utility` barrel into direct imports from `utility/monaco-ssr`. The barrel was bundling two unrelated concerns — lightweight UI helpers (`cn`, `pick`, etc.) and heavy Monaco re-exports — so any story reaching for `cn` transitively pulled Monaco's ESM bundle, which doesn't initialize cleanly inside Storybook's preview iframe. Splitting them keeps UI primitives lightweight. ## Run locally From the repo root: ``` yarn storybook # dev server on http://localhost:6006 yarn build-storybook # static build under packages/graphiql-react/storybook-static ``` Refs: #4219
## Summary Component a11y is covered by Storybook + axe; this is the full-app counterpart. `cypress-axe` runs axe at four checkpoints during a normal session (initial render, after running a query, with the docs panel open, with the history panel open) and gates PRs against a committed baseline. `cypress/.a11y-baseline.json` pins today's accepted violations — color-contrast in several spots, a couple of nested-interactive cases, link-in-text-block in the docs panel. CI fails on net-new only. The spec lives alongside the existing Cypress suite, so it runs as part of the normal `yarn e2e` flow. `cypress.config.ts` gets a small `writeBaseline` Node task so the spec can persist baseline updates from inside the browser. ## Refresh baseline ``` yarn workspace graphiql test:a11y:update ``` Refs: #4219
) ## Summary Component-level a11y for v6. `@storybook/addon-a11y` surfaces axe results next to each story while you're working on it; `@storybook/addon-vitest` folds those same checks into the existing Vitest suite so they run as part of `yarn test` in CI. The model is per-story `parameters.a11y.test`: - `'error'` (default) — axe violations fail the test - `'todo'` — warn only, for stories with known issues we plan to fix - `'off'` — skip a11y for the story `vitest.config.mts` is split into two projects: - `unit` — existing jsdom suite, unchanged behavior - `storybook` — Vitest browser mode (Playwright Chromium), picks up `.stories.*` files The PR CI workflow gets one new step: `yarn playwright install --with-deps chromium` ahead of `yarn test`. ## Run locally ``` yarn workspace @graphiql/react test # both projects yarn workspace @graphiql/react vitest run --project=unit # unit only yarn workspace @graphiql/react vitest run --project=storybook ``` The Storybook a11y panel surfaces the same axe results live during `yarn workspace @graphiql/react storybook`. Refs: #4219
## Summary - Migrate `Button`, `UnStyledButton`, `ToolbarButton`, and `ExecuteButton` CSS to v6 OKLCH tokens. - Add `variant?: 'default' | 'primary'` to `Button`; `primary` renders the Run-button style. - Switch `:focus` to `:focus-visible` on interactive states so the focus ring no longer fires on mouse click. Aligns with [MDN `:focus-visible`](https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-visible) and WCAG 2.4.7 (Focus Visible). - Import `clsx` directly in `button` and `toolbar-button`, following the leaf-module pattern from #4272. A follow-up PR will convert remaining callers and remove the `cn` re-export. - Add Storybook stories: `Primitives/Button` (Default, Primary, Success, Error, Disabled) and `Primitives/ToolbarButton` (Default). ## Test plan - [x] Open Storybook `Primitives/Button` and verify each variant matches the design. - [x] Tab into the buttons, then mouse-click them. Focus ring appears on Tab only, not on click. - [x] Open `Primitives/ToolbarButton`. Hover the icon button; a v6 tooltip appears. - [x] Run `yarn dev:graphiql`. The Run button and toolbar buttons match the new design. Refs: #4219
## Summary Adds `ResponseTreeView`, a custom collapsible tree renderer for GraphQL response JSON. When the user switches to the Tree view in the response pane, they now see the actual response rendered as an expandable tree rather than a placeholder message. Top-level keys are expanded by default; nested objects and arrays start collapsed with a summary showing their child count. Values are colored by type to match the editor's tokenizer palette: strings in blue, numbers in orange, booleans in purple, and null in muted gray. ## Test plan - [x] Open the example app, run a query, and switch the response pane to Tree view. Verify the response renders as a tree with the top level expanded. - [x] Click a collapsed object or array node and confirm it expands to show children. Click again to confirm it collapses. - [x] Confirm `aria-expanded` updates correctly on each toggle button (check with browser DevTools or a screen reader). - [x] Switch back to JSON view and verify the Monaco editor reappears with the response intact. - [x] Run a query that returns nested objects more than two levels deep. Confirm only depth 0 is expanded by default; click to drill in. - [x] Check the tree renders correctly in both dark and light themes.
# Conflicts: # yarn.lock
## Summary
- Fixes the long-standing flake in `docs.cy.ts` → "should show
deprecated arguments category title" that has been intermittently
failing v6 PR CI runs with `Expected to find content: 'Show Deprecated
Arguments' but never did`.
- Root cause is a race against the doc-explorer search debounce; the fix
removes the search dependency entirely.
## Root cause
The test navigated to the `hasArgs` field by typing into the
doc-explorer search box and clicking the first result:
```js
cy.dataCy('doc-explorer-input').type('hasArgs');
cy.dataCy('doc-explorer-option').first().children().first().click();
```
The search box debounces result computation by 200ms (`debounce(200,
...)` in `search.tsx`), and the results list is initialized to the
**empty-query** result set — which matches every type and field. So the
moment the input is focused, a full, stale list of options is already
rendered.
Cypress's `.first()` resolves as soon as *any* matching element exists.
Under CI load, Cypress would grab and click the first option of the
**stale full list** before the 200ms debounce filtered it down to
`hasArgs`. That navigated to the wrong field's docs, where no "Show
Deprecated Arguments" button exists, and the test timed out after
4000ms. Locally the debounce usually settled first, so it passed — a
classic timing-dependent flake.
The sibling "deprecated fields" test never flaked because it navigates
via the type tree and never touches search.
## Changes
- `docs.cy.ts`: navigate to `hasArgs` through the type tree (click the
query type, then the `hasArgs` field row) instead of the debounced
search box, mirroring the reliable "deprecated fields" test. The
field-row name is matched with an anchored regex (`/^hasArgs$/`) since
other fields' descriptions also mention `hasArgs`.
## Validation Steps
- `cd packages/graphiql && CI=true yarn e2e-server 'cypress run --spec
cypress/e2e/docs.cy.ts'`
- The full `docs.cy.ts` suite passes, including the previously-flaky
case. Ran 4× locally with no failures.
## Summary
Adds `ResponseTableView`, the Table view for the response pane. When the
user switches to Table, it walks the response body and renders every
list of objects as an HTML table with sticky headers, each captioned
with its path (e.g. `test.person.friends`) so sibling and aliased lists
each get their own table. Nested objects and arrays in cells are shown
as `Object {n}` / `Array [n]` shorthand to keep rows scannable.
Responses that contain no list field show a clear empty state rather
than a blank panel.
Lists nested deeper than a sibling that already matched stay as `Array
[n]` shorthand rather than getting their own table.
## Test plan
- [ ] Switch to Table view after running a list query and verify rows
and column headers appear
- [ ] Run a query with multiple or aliased sibling lists and verify each
renders as its own table captioned with its path
- [ ] Switch to Table view for a non-list response (e.g. a single object
or scalar) and verify the empty-state message is shown
- [ ] Check that nested object/array cells display `Object {n}` / `Array
[n]` shorthand
- [ ] Confirm ragged rows (objects with different keys) union columns
correctly; missing cells render a placeholder character
- [ ] Verify sticky column headers stay fixed when scrolling a long
result set
- [ ] Review Storybook stories: ListOfObjects, RaggedRows,
NestedObjects, NestedList, SiblingLists, PrimitiveArray, EmptyArray,
NoListField, NoResponse
Refs: #4219
## Summary The variables/headers footer below the operation editor is replaced by a `VarHeadersStrip` component with two tabs: Variables and Headers. The existing JSON editors are reused under their respective tabs. A right-aligned validity hint on the Variables tab shows how many variables are defined and whether they parse as valid JSON. ## Test plan - [ ] Switch between Variables and Headers tabs; each shows the correct editor - [ ] Edit variables JSON; the validity hint updates to reflect the count and valid/invalid state - [ ] Empty variables editor shows no hint - [ ] Existing variables and headers content is preserved when switching tabs - [ ] The strip is vertically resizable using the drag handle above it - [ ] The show/hide toggle collapses and restores the strip - [ ] Active tab persists across page reload (stored in localStorage) Refs: #4219
## Summary The settings dialog exposes segmented controls for theme, density, font size, and header persistence, backed by the `useGraphiQLSettings` hook from #4322. Density and font-size presets fill in the `[data-density]` and `[data-font-size]` token blocks, so changing a preset adjusts spacing and typography; the font-size preset also drives the status bar text, UI icon sizes, and the Monaco editor font. `SettingsDialog` lives in `@graphiql/react` and is wired into the `graphiql` activity bar, replacing the previous inline settings UI; the `forcedTheme` and `showPersistHeadersSettings` props continue to work, with `forcedTheme` hiding the theme control. ## Test plan - [x] On the deploy preview, open the settings dialog from the activity-bar gear; confirm Theme, Density, Font size, and Persist headers controls render. - [x] Change Density (Compact / Comfortable / Spacious) and confirm spacing visibly shifts: top-bar height, activity-rail width, row padding. - [x] Change Font size (Compact / Default / Large / Extra Large) and confirm the editor text, the status bar text, and the toolbar/rail icons all scale. - [x] Switch Theme (Auto / Light / Dark) and confirm the app re-themes. - [x] Toggle Persist headers On, set a header, reload; confirm it is restored only when On. Refs: #4219
## Summary
`useGraphiQLPluginContext()` now returns a `transport` field that lets
plugins register request and response hooks via
`ctx.transport?.onBeforeSend(cb)` and `ctx.transport?.onResponse(cb)`.
Each callback returns a cleanup function for removal. The field is
absent when the host passes a `fetcher` rather than a `transport`, so
plugins can detect the supported path with a simple optional chain. The
hook registry wraps the underlying transport transparently: query,
mutation, subscription, and incremental-delivery flows all pass through
the registered callbacks.
## Test plan
- [x] In a plugin's `content` component, call
`useGraphiQLPluginContext()` and register an `onBeforeSend` hook that
adds a header; run a query and confirm the header reaches the server.
- [x] Register an `onResponse` hook and confirm it fires with the
response envelope after each query or subscription chunk.
- [x] Call the cleanup function returned by `onBeforeSend`; confirm the
hook no longer fires on subsequent requests.
- [x] Mount `<GraphiQLProvider fetcher={...}>` (no `transport`) and
confirm `ctx.transport` is `undefined`.
Refs: #4219
## Summary - `createTransport` now has a coherent method-resolution model driven by `supportedMethods`. The default is POST-only (`method: 'POST'`, `supportedMethods: ['POST']`, no `setMethod`). Pass `['GET', 'POST']` for both, or `['GET']` for GET-only. - When both methods are supported, the active method defaults to POST (unless `opts.method` overrides) and `setMethod` is available to switch at runtime. GET queries encode `query`, `operationName`, and `variables` into the URL with no request body; POST keeps the existing JSON body. - Mutations are handled per the GraphQL-over-HTTP spec: a GET-only transport throws a clear error (a compliant server would return 405), and a both-methods transport with GET active transparently falls back to POST. ## Changes - `createTransport.ts`: method resolution from `supportedMethods` — POST-only default with no `setMethod`; GET-only defaults active method to GET; both-methods defaults to POST with `setMethod` exposed. - GET request encoding: `query`, `operationName`, and `variables` serialized into the URL query string, no body sent. POST requests still carry `Content-Type: application/json` and the spec `Accept` header. - Mutation handling: GET-only transport throws an error mentioning "mutation" and "GET"; both-methods transport sends mutations as POST even when GET is the active method. - Construction- and runtime-time guards: `opts.method` must be a member of `supportedMethods` (throws at construction); `setMethod` validates its argument and throws if unsupported. - `types.ts`: new types for `supportedMethods` / method resolution. `create-fetcher/lib.ts`: updated to the new transport semantics. Adds a changeset. ## Validation Steps 1. From `packages/graphiql-toolkit`, run the transport and fetcher suites: `vitest run src/create-transport src/create-fetcher`. 2. POST-only default: construct with no `supportedMethods`; verify `method` is `'POST'`, `supportedMethods` is `['POST']`, and `setMethod` is `undefined`. 3. GET-only (`supportedMethods: ['GET']`): active method is `'GET'` without passing `opts.method`; a query goes out as GET with params in the URL and no body; a mutation throws an error mentioning "mutation" and "GET". 4. Both methods with `method: 'GET'`: a query encodes params in the URL; a mutation is sent as POST despite GET being active. 5. `opts.method` not in `supportedMethods` throws at construction; `setMethod` to an unsupported method throws at runtime; a valid `setMethod` switches the active method for subsequent requests. Refs: #4219 Spec: https://github.com/graphql/graphql-over-http/blob/main/spec/GraphQLOverHTTP.md
## Summary - The response header now shows the real status, time, and size from `lastResponse` instead of placeholder values. - Still works when the legacy fetcher only provides partial metadata. ## Test plan - [x] Stories render with mock response metadata. - [x] Run a query and confirm the header shows status, time, and size. Refs: #4219
## Summary PR #4349 made `url`, `method`, and `supportedMethods` required on the `Transport` type, but the transport hooks added in #4351 still build `Transport` objects with only `send`. That left `@graphiql/react` failing its type check on `graphiql-6`, which blocks every PR targeting the branch. - `wrap()` now spreads the wrapped transport, so `url`/`method`/`supportedMethods` (and `setMethod`) carry through to the returned transport instead of being dropped. - The transport-hooks specs build their mock transports through a small helper that fills in the required fields. ## Test plan - [x] `@graphiql/react` type-checks cleanly (CI Types Check passes). - [x] The transport-hooks unit tests pass. Refs: #4351, #4349
The top bar rendered a gradient-filled placeholder square where the brand mark belongs. This swaps in the official GraphQL logo, added to `@graphiql/react`'s icon set as `GraphQLLogoIcon`.
`assertLinterMarkWithMessage` triggers Monaco's hover tooltip with one `mousemove`, then looks for the message text. If that fires before Monaco has the latest markers, it computes an empty hover and never retries, so the tooltip never shows and the assertion times out (`Expected to find content: '...' but never did`). The marker is already checked directly via `getModelMarkers`, so only the hover step was flaky. Now it re-triggers the `mousemove` until the message shows, capped, with a direct assertion at the end so real failures still surface. Fixes the intermittent `lint.cy.ts` failure on "Marks syntax errors in variables JSON as error", and the helper change covers the other lint tests too. Same class of `...but never did` flake as #4348, which fixed a similar race in `docs.cy.ts`.
…4362) ## Summary GraphiQL tooltips had no height cap or `z-index`, so long content (for example a markdown deprecation reason) could grow past the viewport and make the whole page scroll, and sticky headers or dialogs could paint over them. Tooltips now cap their height to the space between the trigger and the status bar, scroll any overflow, and render above page content. ## Changes - Add `z-index` so tooltips sit above sticky headers and dialogs. - Cap `max-height` to Radix's measured available height minus the status bar, with `overflow-y: auto`. - Use `box-sizing: border-box` so padding stays within the cap. ## Validation Steps - [x] Hover a trigger with content taller than the viewport (e.g. a field with a long markdown deprecation reason); confirm the tooltip caps height, scrolls, and stops above the status bar instead of growing the page. - [x] Confirm the page does not become scrollable when the tooltip opens, in both Safari and Chrome. - [x] Hover near a sticky header or with a dialog open; confirm the tooltip renders above it. - [x] Confirm short tooltips look unchanged.
) ## Summary New package `@graphiql/plugin-query-builder`, a first-party visual query builder for GraphiQL. People have asked for one since #734. Pick fields from the schema with checkboxes and the operation in the editor stays in sync, both ways. Every change runs through the same parse → mutate → reprint cycle using the `graphql` package's AST utilities, so the builder and the editor never drift apart. What it covers: - **Schema tree.** Root types render as collapsible sections. Scalar fields have checkboxes; object, interface, and union fields are expand-only, since a composite isn't a valid selection without subfields. Checking a nested scalar adds it and any parents; unchecking removes it and prunes anything left empty (including the operation itself when nothing is left). - **Operation-aware.** The builder reads and edits the operation the editor cursor sits in. In a multi-operation document only that operation's root type is enabled; the others collapse. Moving the cursor onto a field expands the tree down to it and highlights it. - **Argument inputs.** Checked fields show inputs matched to each argument's type: scalars (Int/Float as number inputs, Boolean as a checkbox, String/ID as text), enums as a dropdown, lists with an add/remove UI, and input objects as recursive nested editors, including the list-of-input-objects case. - **Variables.** "Use as variable" moves an argument's value into the variables editor and references it as `$name` in the operation; an "Inline argument" toggle reverses it. Suggested names are unique across the whole document, and removing a field cleans up any variable it orphaned. - **Unions and interfaces.** Each concrete type shows as a `... on TypeName` selector; checking a field inside one adds it within that inline fragment. Interface fields shared by every type can also be selected directly, without a type condition. Named fragments already in the document are listed. The plugin is default-installed in the `graphiql` package, so the rail icon is present with no extra configuration. Omit it from your `plugins` list to opt out. This also changes `@graphiql/react`: the active `operationName` now follows the editor cursor, so the Run button, the operation dropdown, and operation-aware plugins reflect the operation you are editing. See the `graphiql@6` migration guide for details. Refs: #734, #4219
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.
Tracking PR for the GraphiQL v6 redesign effort. This is the long-running integration branch that hosts work-in-progress against
main.Individual PRs target
graphiql-6and produce alpha releases via changesets pre-mode. When v6 is ready to ship, this branch will be merged intomain.See discussion #4219 for background and progress updates.
Closes #734 — the visual query builder ships in v6 as
@graphiql/plugin-query-builder.