mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
fix(studio): stop GraphiQL from corrupting other Monaco editors (#47363)
GraphiQL (`@graphiql/react`) runs a second Monaco instance that injects
two global, page-wide styles which corrupt Studio's other editors once a
GraphiQL chunk has loaded (it persists across client-side navigation, so
a full reload hides it). After visiting GraphiQL and returning to e.g.
the SQL editor, the editor collapses to a ~5px sliver and its syntax
colors swap to GraphiQL's theme.
**Changed:**
- `monaco.css` — a higher-specificity counter-rule
(`.monaco-editor.monaco-editor { position: relative !important }`) beats
GraphiQL's runtime-injected `.monaco-editor { position: absolute
!important }`, which otherwise pulls Studio's `@monaco-editor/react`
wrapper out of flow and collapses it to ~5px.
- GraphiQL now uses the primary `supabase` Monaco theme instead of a
separate `supabase-graphql-*` theme, so the global `.mtk*` token palette
stays identical and syntax colors no longer bleed into other editors.
**Added:**
- E2E test (`monaco-graphiql-coexistence.spec.ts`) reproducing both bugs
via client-side SQL editor → GraphiQL → SQL editor navigation (a full
reload unloads the chunk and hides the bug).
- Component test (`CodeEditor.test.tsx`) guarding the height-class
precedence regression from #47339/#47350 — a caller height (e.g. the
email template editor's `h-96`) must win over the default `h-full`.
Covered as a component test since the email source editor isn't
reachable on self-hosted.
## To test
- Open the SQL editor → **Integrations → GraphiQL** → back to the SQL
editor (in-app navigation, not a reload). It should stay full height and
keep its own syntax colors.
- Confirm autocomplete still works in the SQL editor.
- `pnpm --prefix e2e/studio run e2e --
features/monaco-graphiql-coexistence.spec.ts`
- `pnpm --prefix apps/studio test -- CodeEditor.test`
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Improved Monaco editor styling so GraphiQL no longer affects the SQL
editor’s theme or layout when navigating between them.
* Fixed editor sizing so a custom height now takes precedence over the
default full-height setting.
* Polished GraphiQL panel styling for more consistent spacing and
appearance across themes.
* **New Features**
* GraphiQL now uses the shared editor theme for better visual
consistency with Studio.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Alaister Young <10985857+alaister@users.noreply.github.com>
Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
This commit is contained in:
7 files changed
+240
-90
No files matched your search
@@ -0,0 +1,120 @@
|
||||
import { expect, type Page } from '@playwright/test'
|
||||
|
||||
import { env } from '../env.config.js'
|
||||
import { test } from '../utils/test.js'
|
||||
import { toUrl } from '../utils/to-url.js'
|
||||
|
||||
/**
|
||||
* Studio's editors (@monaco-editor/react) and GraphiQL (@graphiql/react) historically ran as
|
||||
* two separate Monaco instances on one page. Each injects Monaco's global CSS (.monaco-editor
|
||||
* layout rules + .mtk* theme classes), and they collide: after visiting GraphiQL the SQL
|
||||
* editor's Monaco wrapper got reflowed to a ~5px slit (covered by the background) and its
|
||||
* syntax colors swapped to GraphiQL's theme.
|
||||
*
|
||||
* IMPORTANT: the bug only reproduces with *client-side* (SPA) navigation. A full `page.goto`
|
||||
* reload unloads GraphiQL's CSS chunk, which removes the offending injected rule — so these
|
||||
* tests navigate via the Next router to keep the chunk loaded, exactly like a real user.
|
||||
*/
|
||||
test.describe('Monaco / GraphiQL coexistence', () => {
|
||||
test.skip(env.IS_PLATFORM, 'Self-hosted mode only — GraphiQL + SQL editor on one local project')
|
||||
|
||||
// Client-side navigation via the Next router (keeps already-loaded chunks/CSS in place).
|
||||
const routerPush = async (page: Page, path: string) => {
|
||||
await page.evaluate((p) => {
|
||||
const router = (window as unknown as { next?: { router?: { push: (p: string) => void } } })
|
||||
.next?.router
|
||||
if (!router) throw new Error('Next router not available for client-side navigation')
|
||||
router.push(p)
|
||||
}, path)
|
||||
}
|
||||
|
||||
const firstEditorHeight = async (page: Page) => {
|
||||
const box = await page.locator('.monaco-editor').first().boundingBox()
|
||||
return box?.height ?? 0
|
||||
}
|
||||
|
||||
const openGraphiQL = async (page: Page, ref: string) => {
|
||||
await routerPush(page, `/project/${ref}/integrations/graphiql/graphiql`)
|
||||
await expect(page.locator('.graphiql-container'), 'GraphiQL container should load').toBeVisible(
|
||||
{ timeout: 30000 }
|
||||
)
|
||||
// GraphiQL mounting its Monaco editor is what injects the global rule that breaks others.
|
||||
await expect(
|
||||
page.locator('.graphiql-query-editor .monaco-editor').first(),
|
||||
'GraphiQL query editor (Monaco) should mount'
|
||||
).toBeVisible({ timeout: 30000 })
|
||||
}
|
||||
|
||||
test('SQL editor stays full height after visiting GraphiQL', async ({ page, ref }) => {
|
||||
// Baseline: a freshly-loaded SQL editor fills its pane.
|
||||
await page.goto(toUrl(`/project/${ref}/sql/new?skip=true`))
|
||||
await expect(
|
||||
page.locator('.monaco-editor').first(),
|
||||
'SQL editor Monaco should render'
|
||||
).toBeVisible({ timeout: 30000 })
|
||||
await expect.poll(() => firstEditorHeight(page), { timeout: 15000 }).toBeGreaterThan(100)
|
||||
|
||||
// Client-side: visit GraphiQL, then come back to the SQL editor.
|
||||
await openGraphiQL(page, ref)
|
||||
await routerPush(page, `/project/${ref}/sql/new?skip=true`)
|
||||
await expect(page, 'should navigate back to SQL editor').toHaveURL(/\/sql\//, {
|
||||
timeout: 30000,
|
||||
})
|
||||
await page.locator('.monaco-editor').first().waitFor({ state: 'attached', timeout: 30000 })
|
||||
|
||||
// With the bug the wrapper collapses to ~5px. It must stay full height.
|
||||
await expect
|
||||
.poll(() => firstEditorHeight(page), {
|
||||
timeout: 15000,
|
||||
message: 'SQL editor must not collapse to a sliver after visiting GraphiQL',
|
||||
})
|
||||
.toBeGreaterThan(100)
|
||||
})
|
||||
|
||||
test('SQL editor keeps its own theme after visiting GraphiQL', async ({ page, ref }) => {
|
||||
await page.goto(toUrl(`/project/${ref}/sql/new?skip=true`))
|
||||
await expect(
|
||||
page.locator('.monaco-editor').first(),
|
||||
'SQL editor Monaco should render'
|
||||
).toBeVisible({ timeout: 30000 })
|
||||
|
||||
await openGraphiQL(page, ref)
|
||||
await routerPush(page, `/project/${ref}/sql/new?skip=true`)
|
||||
await expect(page, 'should navigate back to SQL editor').toHaveURL(/\/sql\//, {
|
||||
timeout: 30000,
|
||||
})
|
||||
|
||||
// Monaco's token colors are global `.mtk*` classes. GraphiQL's gql-argument color (#6c69ce)
|
||||
// must never enter that palette, or every other editor gets rethemed.
|
||||
await expect
|
||||
.poll(
|
||||
() =>
|
||||
page.evaluate(() => {
|
||||
const gqlColor = 'rgb(108, 105, 206)' // #6c69ce — GraphiQL's argument.identifier.gql
|
||||
for (const sheet of Array.from(document.styleSheets)) {
|
||||
let rules: CSSRuleList
|
||||
try {
|
||||
rules = sheet.cssRules
|
||||
} catch {
|
||||
continue
|
||||
}
|
||||
for (const rule of Array.from(rules)) {
|
||||
const styleRule = rule as CSSStyleRule
|
||||
if (
|
||||
/^\.mtk\d+$/.test(styleRule.selectorText || '') &&
|
||||
styleRule.style?.color === gqlColor
|
||||
) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}),
|
||||
{
|
||||
timeout: 15000,
|
||||
message: "GraphiQL's theme must not bleed into the SQL editor's token colors",
|
||||
}
|
||||
)
|
||||
.toBe(false)
|
||||
})
|
||||
})
|
||||
Reference in new issue
Block a user