# `@suaveplan/api-graphql-ui-graphiql` — GraphiQL UI mount at `/graphql/ui` **Package**: `@suaveplan/api-graphql-ui-graphiql` **Status**: PROPOSED **Type**: new-package **Stage**: 7 (GraphQL + WebSocket / SSE engines) — see umbrella `openspec/changes/api-plugin-foundation/` ## Why Per design doc §10.6 the foundation reserves `GET /graphql/ui` for an interactive query explorer but does not bundle one by default — UI choice is opt-in via a separate package so the host's runtime image stays minimal for production deployments that don't need it. GraphiQL is the default option called out in §10.6 ("`GET /graphql/ui` — GraphiQL (or Altair via `api-graphql-ui-altair`)"). Operators wire this package when they want the canonical GraphQL UI; they wire `api-graphql-ui-altair` instead when they prefer Altair. The GraphQL engine adapters (`api-graphql-yoga`, `api-graphql-apollo`) intentionally return 404 at `/graphql/ui` when no UI plugin is wired — so this package is the standard way to give developers an in-browser explorer. ## What changes Net-new package depending on `@suaveplan/api-types`, `@suaveplan/api-router`, `@suaveplan/api-graphql`, `@suaveplan/error`, `@suaveplan/logger`, and `graphiql` (peer; static assets served from this package, not fetched from a CDN). - ADDED: `createGraphiqlUi(opts)` — factory returning a `RouterPlugin` value that registers a `GET ` handler serving the GraphiQL HTML shell + assets. - ADDED: `GraphiqlUiOptions` — Zod-validated config: `uiEndpoint` (default `/graphql/ui`), `graphqlEndpoint` (default `/graphql`), `subscriptionEndpoint` (default `/graphql/ws`), `theme` (`"light"` | `"dark"` | `"system"`, default `"system"`), `defaultQuery`, `defaultVariableEditorOpen`, `editorOptions` (codemirror passthroughs). - ADDED: Static-asset serving — GraphiQL's bundle is served from the package's own `dist/assets/` (no CDN dependency). The HTML shell embeds versioned hashes for cache-busting. - ADDED: CSP/security headers — `Content-Security-Policy: default-src 'self'; style-src 'self' 'unsafe-inline'` (GraphiQL needs inline styles) emitted on the HTML response. - ADDED: `GRAPHQL_UI_GRAPHIQL_*` error codes: `GRAPHQL_UI_GRAPHIQL_ASSET_MISSING`, `GRAPHQL_UI_GRAPHIQL_ENDPOINT_COLLISION`. - ADDED: OTel instruments: `graphql.ui.requests_total { ui: "graphiql" }` counter. ## Out of scope - A CDN-fetched GraphiQL variant — bundled assets only, per the foundation's "no runtime CDN fetches" rule (security + offline-dev posture). - The Altair alternative — separate package `api-graphql-ui-altair`. - Schema introspection — that's the engine adapter's job (Yoga / Apollo configure introspection on `/graphql`). This package just renders an explorer that calls into whatever the engine exposes. - Authentication on the UI route — host config decides whether `/graphql/ui` is behind auth; this package surfaces a `RouterPlugin` whose route options can be configured by the operator. ## Source mapping Genesis-original. Source of design intent: `docs/superpowers/specs/2026-05-13-api-plugin-foundation-design.md`. | Requirement | Design doc section | |---|---| | `GET /graphql/ui` default to GraphiQL | §10.6 | | UI as opt-in via separate package | §10.6 + cross-ref to engine adapters returning 404 when not wired | | Theme / config passthroughs | §10.6 (implied — interactive explorer) | ## Verification approach - `src/factory/create-graphiql-ui.test.ts` — factory returns a valid `RouterPlugin`; HTML response shape; CSP header presence; asset 200s. - `src/factory/create-graphiql-ui.spec-conformance.test.ts` — pins that the mounted endpoint serves at `uiEndpoint` and that the response embeds the configured `graphqlEndpoint` + `subscriptionEndpoint`. - `src/assets/assets.test.ts` — every bundled asset reachable; missing-asset path raises `GRAPHQL_UI_GRAPHIQL_ASSET_MISSING`. - `tests/integration/graphiql-end-to-end.test.ts` (30000ms) — boots `api-graphql-yoga` + this UI plugin, opens a Playwright browser at `/graphql/ui`, runs a query through the live editor, asserts results render. ## Migration impact None — additive only.