feat(studio): paginate Schema Designer via useInfiniteTablesQuery (#46402)

## I have read the
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
file.

YES

## What kind of change does this PR introduce?

Performance improvement / feature

## What is the current behavior?

The Schema Designer fetches all tables in a single request via
`useTablesQuery`. For schemas with 400+ tables this blocks first paint
on a large payload.

## What is the new behavior?

`SchemaGraph` uses `useInfiniteTablesQuery` (pageSize: 100) so the first
100 tables paint immediately. A "Load more tables" button appears above
the legend whenever more pages remain, letting users load the rest on
demand.

## Additional context

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added a "Find table…" selector and keyboard shortcut to quickly locate
and focus tables; supports incremental loading and debounced name search
(with literal wildcard handling).
* Schema Graph shows a bottom "Load more tables" control with loading
state and preserves view after loading more.

* **Refactor**
* Table listing switched to infinite/paginated retrieval and improved
"no tables" logic; server-side name filtering supported.

* **Tests**
* E2E tests add a schema-visualizer wait helper and update flows to
support the paginated visualizer.

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/supabase/supabase/pull/46402?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)

<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Charis authored and GitHub committed 2026-05-29 15:12:22 -04:00
1 parent 91db2d6989
commit a4334a2cc7
11 files changed
+394 -43

No files matched your search

+27 -8
View File
@@ -1,4 +1,4 @@
import { expect } from '@playwright/test'
import { expect, type Page } from '@playwright/test'
import { env } from '../env.config.js'
import { expectClipboardValue } from '../utils/clipboard.js'
@@ -10,8 +10,15 @@ import {
createApiResponseWaiter,
waitForApiResponse,
waitForDatabaseToLoad,
waitForSchemaVisualizerToLoad,
} from '../utils/wait-for-response.js'
async function focusTableInVisualizer(page: Page, tableName: string) {
await page.getByTestId('find-table-selector').click()
await page.getByPlaceholder('Find table…').fill(tableName)
await page.getByRole('option', { name: tableName, exact: true }).click()
}
test.describe('Database', () => {
test.describe('Schema Visualizer', () => {
test('actions works as expected', async ({ page, ref }) => {
@@ -25,15 +32,19 @@ test.describe('Database', () => {
await dropTable(databaseTableName)
}
)
const wait = createApiResponseWaiter(
page,
'pg-meta',
ref,
'tables?include_columns=true&included_schemas=public'
'query?key=project:default-schema:public-infinite_tables'
)
await page.goto(toUrl(`/project/${env.PROJECT_REF}/database/schemas?schema=public`))
await wait
// focus the test table so it mounts inside the viewport
await focusTableInVisualizer(page, databaseTableName)
// validates table and column exists
await expect(page.getByText(databaseTableName, { exact: true })).toBeVisible()
await expect(page.getByText(databaseColumnName, { exact: true })).toBeVisible()
@@ -69,10 +80,12 @@ test.describe('Database', () => {
// changing schema -> auth
await page.getByTestId('schema-selector').click()
await page.getByRole('option', { name: 'auth' }).click()
await waitForDatabaseToLoad(page, ref, 'auth')
await expect(page.getByText('users', { exact: true })).toBeVisible()
await expect(page.getByText('sso_providers', { exact: true })).toBeVisible()
await expect(page.getByText('saml_providers', { exact: true })).toBeVisible()
await waitForSchemaVisualizerToLoad(page, ref, 'auth')
for (const tableName of ['users', 'sso_providers', 'saml_providers']) {
await focusTableInVisualizer(page, tableName)
await expect(page.getByText(tableName, { exact: true })).toBeVisible()
}
})
test('table actions work as expected', async ({ page, ref }) => {
@@ -90,11 +103,14 @@ test.describe('Database', () => {
page,
'pg-meta',
ref,
'tables?include_columns=true&included_schemas=public'
'query?key=project:default-schema:public-infinite_tables'
)
await page.goto(toUrl(`/project/${env.PROJECT_REF}/database/schemas?schema=public`))
await wait
// focus the test table so it mounts inside the viewport
await focusTableInVisualizer(page, databaseTableName)
// validates table and column exists
await expect(page.getByText(databaseTableName, { exact: true })).toBeVisible()
// test we can edit the column
@@ -158,11 +174,14 @@ test.describe('Database', () => {
page,
'pg-meta',
ref,
'tables?include_columns=true&included_schemas=public'
'query?key=project:default-schema:public-infinite_tables'
)
await page.goto(toUrl(`/project/${env.PROJECT_REF}/database/schemas?schema=public`))
await wait
// focus the test table so it mounts inside the viewport
await focusTableInVisualizer(page, databaseTableName)
// validates table and column exists
await expect(page.getByText(databaseTableName, { exact: true })).toBeVisible()
await expect(page.getByText(databaseColumnName, { exact: true })).toBeVisible()