From 151a1792d92ce90fbcc9fd15800e40c5dc597b4f Mon Sep 17 00:00:00 2001 From: Coenen Benjamin Date: Wed, 29 Apr 2026 21:42:05 +0200 Subject: [PATCH 01/21] feat(studio): add support for new ducklake destination in replication UI (#45370) --- .../DestinationForm/DestinationForm.schema.ts | 12 + .../DestinationForm.utils.test.ts | 166 +++++++++++ .../DestinationForm/DestinationForm.utils.ts | 133 ++++++++- .../DestinationPanelFields.tsx | 278 ++++++++++++++++++ .../DestinationForm/index.tsx | 48 ++- .../DestinationPanel/DestinationPanel.tsx | 1 + .../DestinationPanel.types.ts | 2 +- .../DestinationTypeSelection.tsx | 43 ++- .../Database/Replication/DestinationRow.tsx | 2 + .../Database/Replication/Destinations.tsx | 12 +- .../Replication/ReplicationDiagram/Nodes.tsx | 23 +- .../ReplicationDiagram/Nodes.utils.test.ts | 22 ++ .../ReplicationDiagram/Nodes.utils.ts | 11 + .../Replication/useDestinationInformation.ts | 11 +- .../Replication/useIsETLPrivateAlpha.ts | 7 +- .../create-destination-pipeline-mutation.ts | 49 ++- .../update-destination-pipeline-mutation.ts | 31 +- .../validate-destination-mutation.ts | 32 +- 18 files changed, 851 insertions(+), 32 deletions(-) create mode 100644 apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationForm/DestinationForm.utils.test.ts create mode 100644 apps/studio/components/interfaces/Database/Replication/ReplicationDiagram/Nodes.utils.test.ts create mode 100644 apps/studio/components/interfaces/Database/Replication/ReplicationDiagram/Nodes.utils.ts diff --git a/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationForm/DestinationForm.schema.ts b/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationForm/DestinationForm.schema.ts index d2ff933585d..41816878725 100644 --- a/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationForm/DestinationForm.schema.ts +++ b/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationForm/DestinationForm.schema.ts @@ -32,6 +32,18 @@ export const DestinationPanelFormSchema = z.object({ s3AccessKeyId: z.string().optional(), s3SecretAccessKey: z.string().optional(), s3Region: z.string().optional(), + // DuckLake fields + ducklakeCatalogUrl: z.string().optional(), + ducklakeDataPath: z.string().optional(), + ducklakePoolSize: z.number().int().min(1).max(6).optional(), + ducklakeS3AccessKeyId: z.string().optional(), + ducklakeS3SecretAccessKey: z.string().optional(), + ducklakeS3Region: z.string().optional(), + ducklakeS3Endpoint: z.string().optional(), + ducklakeS3UrlStyle: z.enum(['path', 'vhost']).optional(), + ducklakeS3UseSsl: z.boolean().optional(), + ducklakeMetadataSchema: z.string().optional(), + ducklakeExpireSnapshotsOlderThan: z.string().optional(), }) export type DestinationPanelSchemaType = z.infer diff --git a/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationForm/DestinationForm.utils.test.ts b/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationForm/DestinationForm.utils.test.ts new file mode 100644 index 00000000000..65458c9202d --- /dev/null +++ b/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationForm/DestinationForm.utils.test.ts @@ -0,0 +1,166 @@ +import { describe, expect, it, vi } from 'vitest' + +import { + buildDestinationConfig, + buildDestinationConfigForValidation, + getDucklakeValidationIssues, +} from './DestinationForm.utils' + +const baseDucklakeFormData = { + name: 'DuckLake Destination', + publicationName: 'pub', + maxFillMs: undefined, + maxTableSyncWorkers: undefined, + maxCopyConnectionsPerTable: undefined, + invalidatedSlotBehavior: undefined, + projectId: undefined, + datasetId: undefined, + serviceAccountKey: undefined, + connectionPoolSize: undefined, + maxStalenessMins: undefined, + warehouseName: undefined, + namespace: undefined, + newNamespaceName: undefined, + catalogToken: undefined, + s3AccessKeyId: undefined, + s3SecretAccessKey: undefined, + s3Region: undefined, + ducklakeCatalogUrl: 'postgres://user:pass@host:5432/catalog', + ducklakeDataPath: 's3://bucket/path', + ducklakePoolSize: 4, + ducklakeS3AccessKeyId: ' access-key ', + ducklakeS3SecretAccessKey: ' secret-key ', + ducklakeS3Region: ' eu-west-1 ', + ducklakeS3Endpoint: ' s3.example.com ', + ducklakeS3UrlStyle: 'path' as const, + ducklakeS3UseSsl: true, + ducklakeMetadataSchema: ' ducklake_metadata ', + ducklakeExpireSnapshotsOlderThan: ' 7 days ', +} + +describe('DestinationForm.utils DuckLake', () => { + it('builds DuckLake validation config with required fields trimmed and blank optionals removed', () => { + const config = buildDestinationConfigForValidation({ + projectRef: 'project-ref', + selectedType: 'DuckLake', + data: { + ...baseDucklakeFormData, + ducklakeMetadataSchema: ' ', + ducklakeExpireSnapshotsOlderThan: ' ', + }, + }) + + expect(config).toEqual({ + ducklake: { + catalogUrl: 'postgres://user:pass@host:5432/catalog', + dataPath: 's3://bucket/path', + poolSize: 4, + s3AccessKeyId: 'access-key', + s3SecretAccessKey: 'secret-key', + s3Region: 'eu-west-1', + s3Endpoint: 's3.example.com', + s3UrlStyle: 'path', + s3UseSsl: true, + metadataSchema: undefined, + expireSnapshotsOlderThan: undefined, + }, + }) + }) + + it('builds DuckLake submit config with normalized values', async () => { + const createS3AccessKey = vi.fn() + const resolveNamespace = vi.fn() + + const config = await buildDestinationConfig({ + projectRef: 'project-ref', + selectedType: 'DuckLake', + data: baseDucklakeFormData, + createS3AccessKey, + resolveNamespace, + }) + + expect(config).toEqual({ + ducklake: { + catalogUrl: 'postgres://user:pass@host:5432/catalog', + dataPath: 's3://bucket/path', + poolSize: 4, + s3AccessKeyId: 'access-key', + s3SecretAccessKey: 'secret-key', + s3Region: 'eu-west-1', + s3Endpoint: 's3.example.com', + s3UrlStyle: 'path', + s3UseSsl: true, + metadataSchema: 'ducklake_metadata', + expireSnapshotsOlderThan: '7 days', + }, + }) + expect(createS3AccessKey).not.toHaveBeenCalled() + expect(resolveNamespace).not.toHaveBeenCalled() + }) + + it('returns required-field errors for missing DuckLake settings', () => { + const issues = getDucklakeValidationIssues({ + ducklakeCatalogUrl: '', + ducklakeDataPath: '', + ducklakeS3AccessKeyId: '', + ducklakeS3SecretAccessKey: '', + ducklakeS3Region: '', + ducklakeS3Endpoint: '', + ducklakeMetadataSchema: '', + }) + + expect(issues).toEqual([ + { path: 'ducklakeCatalogUrl', message: 'Catalog URL is required' }, + { path: 'ducklakeDataPath', message: 'Data path is required' }, + { path: 'ducklakeS3AccessKeyId', message: 'S3 Access Key ID is required' }, + { path: 'ducklakeS3SecretAccessKey', message: 'S3 Secret Access Key is required' }, + { path: 'ducklakeS3Region', message: 'S3 Region is required' }, + { path: 'ducklakeS3Endpoint', message: 'S3 Endpoint is required' }, + ]) + }) + + it('returns format errors for invalid DuckLake values', () => { + const issues = getDucklakeValidationIssues({ + ducklakeCatalogUrl: 'mysql://catalog', + ducklakeDataPath: 'file://bucket/path', + ducklakeS3AccessKeyId: 'access-key', + ducklakeS3SecretAccessKey: 'secret-key', + ducklakeS3Region: 'eu-west-1', + ducklakeS3Endpoint: 'https://s3.example.com', + ducklakeMetadataSchema: 'ducklake-schema', + }) + + expect(issues).toEqual([ + { + path: 'ducklakeCatalogUrl', + message: 'DuckLake catalog URL must be a PostgreSQL-compatible URL', + }, + { + path: 'ducklakeDataPath', + message: 'DuckLake data path must start with s3:// and cannot contain file://', + }, + { + path: 'ducklakeS3Endpoint', + message: 'S3 endpoint should not contain the protocol scheme', + }, + { + path: 'ducklakeMetadataSchema', + message: 'DuckLake metadata schema must contain only letters, numbers, and underscores', + }, + ]) + }) + + it('accepts a valid DuckLake configuration', () => { + expect( + getDucklakeValidationIssues({ + ducklakeCatalogUrl: 'postgresql://user:pass@host:5432/catalog', + ducklakeDataPath: 's3://bucket/path', + ducklakeS3AccessKeyId: 'access-key', + ducklakeS3SecretAccessKey: 'secret-key', + ducklakeS3Region: 'eu-west-1', + ducklakeS3Endpoint: 's3.example.com', + ducklakeMetadataSchema: 'ducklake_schema_1', + }) + ).toEqual([]) + }) +}) diff --git a/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationForm/DestinationForm.utils.ts b/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationForm/DestinationForm.utils.ts index e9801c0f202..a5827e6c959 100644 --- a/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationForm/DestinationForm.utils.ts +++ b/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationForm/DestinationForm.utils.ts @@ -4,10 +4,14 @@ import z from 'zod' import { DestinationType } from '../DestinationPanel.types' import { CREATE_NEW_KEY, CREATE_NEW_NAMESPACE } from './DestinationForm.constants' -import { DestinationPanelFormSchema } from './DestinationForm.schema' +import { + DestinationPanelFormSchema, + type DestinationPanelSchemaType, +} from './DestinationForm.schema' import { BigQueryDestinationConfig, DestinationConfig, + DucklakeDestinationConfig, IcebergDestinationConfig, } from '@/data/replication/create-destination-pipeline-mutation' import { @@ -16,6 +20,102 @@ import { } from '@/data/storage/s3-access-key-create-mutation' import { ResponseError } from '@/types' +const normalizeOptionalString = (value?: string) => { + const trimmed = value?.trim() + return trimmed ? trimmed : undefined +} + +const normalizeRequiredString = (value?: string) => value?.trim() ?? '' + +type DucklakeFieldPath = + | 'ducklakeCatalogUrl' + | 'ducklakeDataPath' + | 'ducklakeS3AccessKeyId' + | 'ducklakeS3SecretAccessKey' + | 'ducklakeS3Region' + | 'ducklakeS3Endpoint' + | 'ducklakeMetadataSchema' + +export type DucklakeValidationIssue = { + path: DucklakeFieldPath + message: string +} + +export const getDucklakeValidationIssues = ( + data: Pick< + DestinationPanelSchemaType, + | 'ducklakeCatalogUrl' + | 'ducklakeDataPath' + | 'ducklakeS3AccessKeyId' + | 'ducklakeS3SecretAccessKey' + | 'ducklakeS3Region' + | 'ducklakeS3Endpoint' + | 'ducklakeMetadataSchema' + > +): DucklakeValidationIssue[] => { + const issues: DucklakeValidationIssue[] = [] + + if (!data.ducklakeCatalogUrl?.length) { + issues.push({ path: 'ducklakeCatalogUrl', message: 'Catalog URL is required' }) + } else if ( + !data.ducklakeCatalogUrl.startsWith('postgres://') && + !data.ducklakeCatalogUrl.startsWith('postgresql://') + ) { + issues.push({ + path: 'ducklakeCatalogUrl', + message: 'DuckLake catalog URL must be a PostgreSQL-compatible URL', + }) + } + + if (!data.ducklakeDataPath?.length) { + issues.push({ path: 'ducklakeDataPath', message: 'Data path is required' }) + } else if ( + !data.ducklakeDataPath.startsWith('s3://') || + data.ducklakeDataPath.includes('file://') + ) { + issues.push({ + path: 'ducklakeDataPath', + message: 'DuckLake data path must start with s3:// and cannot contain file://', + }) + } + + if (!data.ducklakeS3AccessKeyId?.length) { + issues.push({ path: 'ducklakeS3AccessKeyId', message: 'S3 Access Key ID is required' }) + } + + if (!data.ducklakeS3SecretAccessKey?.length) { + issues.push({ + path: 'ducklakeS3SecretAccessKey', + message: 'S3 Secret Access Key is required', + }) + } + + if (!data.ducklakeS3Region?.length) { + issues.push({ path: 'ducklakeS3Region', message: 'S3 Region is required' }) + } + + if (!data.ducklakeS3Endpoint?.length) { + issues.push({ path: 'ducklakeS3Endpoint', message: 'S3 Endpoint is required' }) + } else if ( + data.ducklakeS3Endpoint.startsWith('http://') || + data.ducklakeS3Endpoint.startsWith('https://') + ) { + issues.push({ + path: 'ducklakeS3Endpoint', + message: 'S3 endpoint should not contain the protocol scheme', + }) + } + + if (data.ducklakeMetadataSchema && !/^[A-Za-z0-9_]+$/.test(data.ducklakeMetadataSchema)) { + issues.push({ + path: 'ducklakeMetadataSchema', + message: 'DuckLake metadata schema must contain only letters, numbers, and underscores', + }) + } + + return issues +} + // Helper function to build destination config for validation export const buildDestinationConfigForValidation = ({ projectRef, @@ -63,6 +163,22 @@ export const buildDestinationConfigForValidation = ({ s3Region: data.s3Region ?? '', }, } + } else if (selectedType === 'DuckLake') { + return { + ducklake: { + catalogUrl: data.ducklakeCatalogUrl ?? '', + dataPath: data.ducklakeDataPath ?? '', + poolSize: data.ducklakePoolSize, + s3AccessKeyId: normalizeRequiredString(data.ducklakeS3AccessKeyId), + s3SecretAccessKey: normalizeRequiredString(data.ducklakeS3SecretAccessKey), + s3Region: normalizeRequiredString(data.ducklakeS3Region), + s3Endpoint: normalizeRequiredString(data.ducklakeS3Endpoint), + s3UrlStyle: data.ducklakeS3UrlStyle, + s3UseSsl: data.ducklakeS3UseSsl, + metadataSchema: normalizeOptionalString(data.ducklakeMetadataSchema), + expireSnapshotsOlderThan: normalizeOptionalString(data.ducklakeExpireSnapshotsOlderThan), + }, + } } else { throw new Error('Invalid destination type') } @@ -127,6 +243,21 @@ export const buildDestinationConfig = async ({ s3Region: data.s3Region ?? '', } destinationConfig = { iceberg: icebergConfig } + } else if (selectedType === 'DuckLake') { + const ducklakeConfig: DucklakeDestinationConfig = { + catalogUrl: data.ducklakeCatalogUrl ?? '', + dataPath: data.ducklakeDataPath ?? '', + poolSize: data.ducklakePoolSize, + s3AccessKeyId: normalizeRequiredString(data.ducklakeS3AccessKeyId), + s3SecretAccessKey: normalizeRequiredString(data.ducklakeS3SecretAccessKey), + s3Region: normalizeRequiredString(data.ducklakeS3Region), + s3Endpoint: normalizeRequiredString(data.ducklakeS3Endpoint), + s3UrlStyle: data.ducklakeS3UrlStyle, + s3UseSsl: data.ducklakeS3UseSsl, + metadataSchema: normalizeOptionalString(data.ducklakeMetadataSchema), + expireSnapshotsOlderThan: normalizeOptionalString(data.ducklakeExpireSnapshotsOlderThan), + } + destinationConfig = { ducklake: ducklakeConfig } } return destinationConfig diff --git a/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationForm/DestinationPanelFields.tsx b/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationForm/DestinationPanelFields.tsx index 29340472499..8b72c53548c 100644 --- a/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationForm/DestinationPanelFields.tsx +++ b/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationForm/DestinationPanelFields.tsx @@ -93,6 +93,284 @@ export const BigQueryFields = ({ form }: { form: UseFormReturn }) => { + const [showCatalogUrl, setShowCatalogUrl] = useState(false) + const [showSecretAccessKey, setShowSecretAccessKey] = useState(false) + + return ( +
+

DuckLake settings

+ +
+

Catalog

+

+ Configure the PostgreSQL-backed DuckLake catalog and the S3-compatible storage location + for replicated data. +

+
+ +
+ ( + + + field.onChange(event.target.value)} + actions={ +
+
+ } + /> +
+
+ )} + /> + + ( + + + + + + )} + /> + + ( + + + + field.onChange( + event.target.value === '' ? undefined : Number(event.target.value) + ) + } + /> + + + )} + /> +
+ +
+

Object storage

+

+ Optional credentials and endpoint settings for S3-compatible storage providers. +

+
+ +
+ ( + + + + + + )} + /> + + ( + + + + +
+ +
+

Maintenance

+

+ Optional settings for DuckLake metadata tables and snapshot cleanup. +

+
+ +
+ ( + + + + + + )} + /> + + ( + + + + + + )} + /> +
+
+ ) +} + /** * [Joshen] JFYI I'd foresee a possible UX friction point here regarding S3 access key IDs and secret access keys * - We'd allow users to select access key IDs via a dropdown here, but require a text input for secret access keys diff --git a/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationForm/index.tsx b/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationForm/index.tsx index 88e5ccabd08..6632bf4442e 100644 --- a/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationForm/index.tsx +++ b/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationForm/index.tsx @@ -11,6 +11,7 @@ import * as z from 'zod' import { useIsETLBigQueryPrivateAlpha, + useIsETLDucklakePrivateAlpha, useIsETLIcebergPrivateAlpha, } from '../../useIsETLPrivateAlpha' import { DestinationType } from '../DestinationPanel.types' @@ -20,9 +21,10 @@ import { DestinationPanelFormSchema as FormSchema } from './DestinationForm.sche import { buildDestinationConfig, buildDestinationConfigForValidation, + getDucklakeValidationIssues, } from './DestinationForm.utils' import { DestinationNameInput } from './DestinationNameInput' -import { AnalyticsBucketFields, BigQueryFields } from './DestinationPanelFields' +import { AnalyticsBucketFields, BigQueryFields, DuckLakeFields } from './DestinationPanelFields' import { NewPublicationPanel } from './NewPublicationPanel' import { NoDestinationsAvailable } from './NoDestinationsAvailable' import { PublicationSelection } from './PublicationSelection' @@ -70,6 +72,20 @@ interface DestinationFormProps { onClose: () => void } +type DucklakeApiConfig = { + catalog_url: string + data_path: string + pool_size?: number + s3_access_key_id?: string + s3_secret_access_key?: string + s3_region?: string + s3_endpoint?: string + s3_url_style?: 'path' | 'vhost' + s3_use_ssl?: boolean + metadata_schema?: string + expire_snapshots_older_than?: string +} + export const DestinationForm = ({ selectedType, visible, @@ -81,6 +97,7 @@ export const DestinationForm = ({ const etlEnableBigQuery = useIsETLBigQueryPrivateAlpha() const etlEnableIceberg = useIsETLIcebergPrivateAlpha() + const etlEnableDucklake = useIsETLDucklakePrivateAlpha() const { can: canReadAPIKeys } = useAsyncCheckPermissions(PermissionAction.SECRETS_READ, '*') const [isFormInteracting, setIsFormInteracting] = useState(false) @@ -108,8 +125,9 @@ export const DestinationForm = ({ if (etlEnableBigQuery) destinations.push({ value: 'BigQuery', label: 'BigQuery' }) if (etlEnableIceberg) destinations.push({ value: 'Analytics Bucket', label: 'Analytics Bucket' }) + if (etlEnableDucklake) destinations.push({ value: 'DuckLake', label: 'DuckLake' }) return destinations - }, [etlEnableBigQuery, etlEnableIceberg]) + }, [etlEnableBigQuery, etlEnableDucklake, etlEnableIceberg]) const hasNoAvailableDestinations = availableDestinations.length === 0 const { data: sourcesData } = useReplicationSourcesQuery({ projectRef }) @@ -171,6 +189,14 @@ export const DestinationForm = ({ const config = destinationData?.config const isBigQueryConfig = config && 'big_query' in config const isIcebergConfig = config && 'iceberg' in config + const ducklakeConfigValue = + config && 'ducklake' in (config as Record) + ? (config as Record).ducklake + : undefined + const ducklakeConfig = + ducklakeConfigValue && typeof ducklakeConfigValue === 'object' + ? (ducklakeConfigValue as DucklakeApiConfig) + : undefined return { // Common fields @@ -199,6 +225,18 @@ export const DestinationForm = ({ s3SecretAccessKey: isIcebergConfig ? config.iceberg.supabase.s3_secret_access_key : '', s3Region: projectSettings?.region ?? (isIcebergConfig ? config.iceberg.supabase.s3_region : ''), + // DuckLake fields + ducklakeCatalogUrl: ducklakeConfig?.catalog_url ?? '', + ducklakeDataPath: ducklakeConfig?.data_path ?? '', + ducklakePoolSize: ducklakeConfig?.pool_size, + ducklakeS3AccessKeyId: ducklakeConfig?.s3_access_key_id ?? '', + ducklakeS3SecretAccessKey: ducklakeConfig?.s3_secret_access_key ?? '', + ducklakeS3Region: ducklakeConfig?.s3_region ?? '', + ducklakeS3Endpoint: ducklakeConfig?.s3_endpoint ?? '', + ducklakeS3UrlStyle: ducklakeConfig?.s3_url_style ?? 'path', + ducklakeS3UseSsl: ducklakeConfig?.s3_use_ssl ?? true, + ducklakeMetadataSchema: ducklakeConfig?.metadata_schema ?? 'ducklake', + ducklakeExpireSnapshotsOlderThan: ducklakeConfig?.expire_snapshots_older_than ?? '', } }, [destinationData, pipelineData, catalogToken, projectSettings]) @@ -244,6 +282,10 @@ export const DestinationForm = ({ if (data.s3AccessKeyId !== 'create-new' && !data.s3SecretAccessKey?.length) { addRequiredFieldError('s3SecretAccessKey', 'S3 Secret Access Key is required') } + } else if (selectedType === 'DuckLake') { + getDucklakeValidationIssues(data).forEach(({ path, message }) => { + addRequiredFieldError(path, message) + }) } }) ), @@ -536,6 +578,8 @@ export const DestinationForm = ({ setIsFormInteracting={setIsFormInteracting} onSelectNewBucket={() => setNewBucketSheetVisible(true)} /> + ) : selectedType === 'DuckLake' && etlEnableDucklake ? ( + ) : null} diff --git a/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationPanel.tsx b/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationPanel.tsx index 53d1d953471..eba2fbd08c3 100644 --- a/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationPanel.tsx +++ b/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationPanel.tsx @@ -41,6 +41,7 @@ export const DestinationPanel = ({ onSuccessCreateReadReplica }: DestinationPane 'Read Replica', 'BigQuery', 'Analytics Bucket', + 'DuckLake', ]).withOptions({ history: 'push', clearOnDefault: true, diff --git a/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationPanel.types.ts b/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationPanel.types.ts index 657395b8b66..1a14188cfa1 100644 --- a/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationPanel.types.ts +++ b/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationPanel.types.ts @@ -1 +1 @@ -export type DestinationType = 'Read Replica' | 'BigQuery' | 'Analytics Bucket' +export type DestinationType = 'Read Replica' | 'BigQuery' | 'Analytics Bucket' | 'DuckLake' diff --git a/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationTypeSelection.tsx b/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationTypeSelection.tsx index 754ae1cd9be..08f9ab6db68 100644 --- a/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationTypeSelection.tsx +++ b/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationTypeSelection.tsx @@ -3,7 +3,11 @@ import { parseAsInteger, parseAsStringEnum, useQueryState } from 'nuqs' import { Badge, cn, RadioGroupStacked, RadioGroupStackedItem } from 'ui' import { useDestinationInformation } from '../useDestinationInformation' -import { useIsETLBigQueryPrivateAlpha, useIsETLIcebergPrivateAlpha } from '../useIsETLPrivateAlpha' +import { + useIsETLBigQueryPrivateAlpha, + useIsETLDucklakePrivateAlpha, + useIsETLIcebergPrivateAlpha, +} from '../useIsETLPrivateAlpha' import { DestinationType } from './DestinationPanel.types' import { InlineLink } from '@/components/ui/InlineLink' import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled' @@ -11,11 +15,15 @@ import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled' export const DestinationTypeSelection = () => { const etlEnableBigQuery = useIsETLBigQueryPrivateAlpha() const etlEnableIceberg = useIsETLIcebergPrivateAlpha() + const etlEnableDucklake = useIsETLDucklakePrivateAlpha() const { infrastructureReadReplicas } = useIsFeatureEnabled(['infrastructure:read_replicas']) - const numberOfTypes = [infrastructureReadReplicas, etlEnableBigQuery, etlEnableIceberg].filter( - Boolean - ).length + const numberOfTypes = [ + infrastructureReadReplicas, + etlEnableBigQuery, + etlEnableIceberg, + etlEnableDucklake, + ].filter(Boolean).length const [urlDestinationType, setDestinationType] = useQueryState( 'destinationType', @@ -23,6 +31,7 @@ export const DestinationTypeSelection = () => { 'Read Replica', 'BigQuery', 'Analytics Bucket', + 'DuckLake', ]).withOptions({ history: 'push', clearOnDefault: true, @@ -51,8 +60,12 @@ export const DestinationTypeSelection = () => { value={destinationType} onValueChange={(value) => setDestinationType(value as DestinationType)} className={cn( - 'grid [&>button>div]:py-4 grid-cols-3', - numberOfTypes === 3 && !editMode ? 'grid-cols-3' : 'grid-cols-2', + 'grid [&>button>div]:py-4', + !editMode && numberOfTypes >= 4 + ? 'grid-cols-4' + : !editMode && numberOfTypes === 3 + ? 'grid-cols-3' + : 'grid-cols-2', '[&>button:first-of-type]:rounded-none [&>button:last-of-type]:rounded-none', '[&>button:first-of-type]:!rounded-l-lg [&>button:last-of-type]:!rounded-r-lg' )} @@ -118,6 +131,24 @@ export const DestinationTypeSelection = () => { )} + + {((!editMode && etlEnableDucklake) || (editMode && destinationType === 'DuckLake')) && ( + +
+ +
+
+

DuckLake

+ Alpha +
+

+ Send data to a DuckLake catalog backed by S3-compatible object storage for + flexible lakehouse workflows +

+
+
+
+ )} {destinationType !== 'Read Replica' && ( diff --git a/apps/studio/components/interfaces/Database/Replication/DestinationRow.tsx b/apps/studio/components/interfaces/Database/Replication/DestinationRow.tsx index a6d0cbeb8fb..7862020cac6 100644 --- a/apps/studio/components/interfaces/Database/Replication/DestinationRow.tsx +++ b/apps/studio/components/interfaces/Database/Replication/DestinationRow.tsx @@ -138,6 +138,8 @@ export const DestinationRow = ({ destinationId }: DestinationRowProps) => { ) : type === 'Analytics Bucket' ? ( + ) : type === 'DuckLake' ? ( + ) : ( )} diff --git a/apps/studio/components/interfaces/Database/Replication/Destinations.tsx b/apps/studio/components/interfaces/Database/Replication/Destinations.tsx index 56e55e6876a..c25e0a6ca06 100644 --- a/apps/studio/components/interfaces/Database/Replication/Destinations.tsx +++ b/apps/studio/components/interfaces/Database/Replication/Destinations.tsx @@ -29,7 +29,11 @@ import { DestinationRow } from './DestinationRow' import { DisableExternalReplicationDialog } from './DisableExternalReplicationDialog' import { PIPELINE_ERROR_MESSAGES } from './Pipeline.utils' import { ReadReplicaRow } from './ReadReplicas/ReadReplicaRow' -import { useIsETLBigQueryPrivateAlpha, useIsETLIcebergPrivateAlpha } from './useIsETLPrivateAlpha' +import { + useIsETLBigQueryPrivateAlpha, + useIsETLDucklakePrivateAlpha, + useIsETLIcebergPrivateAlpha, +} from './useIsETLPrivateAlpha' import { AlertError } from '@/components/ui/AlertError' import { DocsButton } from '@/components/ui/DocsButton' import { useReadReplicasQuery } from '@/data/read-replicas/replicas-query' @@ -47,6 +51,7 @@ export const Destinations = () => { const etlEnableBigQuery = useIsETLBigQueryPrivateAlpha() const etlEnableIceberg = useIsETLIcebergPrivateAlpha() + const etlEnableDucklake = useIsETLDucklakePrivateAlpha() const { infrastructureReadReplicas } = useIsFeatureEnabled(['infrastructure:read_replicas']) const newDestinationDefaultType = infrastructureReadReplicas @@ -55,7 +60,9 @@ export const Destinations = () => { ? 'BigQuery' : etlEnableIceberg ? 'Analytics Bucket' - : null + : etlEnableDucklake + ? 'DuckLake' + : null const prefetchedRef = useRef(false) const [filterString, setFilterString] = useState('') @@ -69,6 +76,7 @@ export const Destinations = () => { 'Read Replica', 'BigQuery', 'Analytics Bucket', + 'DuckLake', ]).withOptions({ history: 'push', clearOnDefault: true, diff --git a/apps/studio/components/interfaces/Database/Replication/ReplicationDiagram/Nodes.tsx b/apps/studio/components/interfaces/Database/Replication/ReplicationDiagram/Nodes.tsx index 247cef005ce..d8f275edc99 100644 --- a/apps/studio/components/interfaces/Database/Replication/ReplicationDiagram/Nodes.tsx +++ b/apps/studio/components/interfaces/Database/Replication/ReplicationDiagram/Nodes.tsx @@ -1,13 +1,14 @@ import { Handle, Position } from '@xyflow/react' import { useParams } from 'common' import { AnalyticsBucket, BigQuery, Database } from 'icons' -import { PropsWithChildren, useMemo } from 'react' +import { ComponentType, PropsWithChildren, useMemo } from 'react' import { AWS_REGIONS } from 'shared-data' import { cn, Tooltip, TooltipContent, TooltipTrigger } from 'ui' import { getStatusName } from '../Pipeline.utils' import { getStatusLabel } from '../ReadReplicas/ReadReplicas.utils' import { STATUS_REFRESH_FREQUENCY_MS } from '../Replication.constants' +import { getReplicationDestinationType, type ReplicationDestinationType } from './Nodes.utils' import { useReadReplicasQuery } from '@/data/read-replicas/replicas-query' import { formatDatabaseID } from '@/data/read-replicas/replicas.utils' import { useReplicationDestinationsQuery } from '@/data/replication/destinations-query' @@ -18,6 +19,15 @@ import { BASE_PATH } from '@/lib/constants' export const NODE_WIDTH = 480 +const destinationIconByType: Record< + ReplicationDestinationType, + ComponentType<{ className?: string; size?: string | number }> +> = { + BigQuery, + 'Analytics Bucket': AnalyticsBucket, + DuckLake: Database, +} + const NodeContainer = ({ className, children }: PropsWithChildren<{ className?: string }>) => { return (
{ ) const statusName = getStatusName(pipelineStatusData?.status) - const config = destination?.config ?? {} - const type = - 'big_query' in config ? 'BigQuery' : 'iceberg' in config ? 'Analytics Bucket' : undefined + const type = getReplicationDestinationType(destination?.config) + const DestinationIcon = type ? destinationIconByType[type] : undefined return ( - {type === 'BigQuery' ? ( - - ) : type === 'Analytics Bucket' ? ( - - ) : null} + {DestinationIcon ? : null}

{type}

diff --git a/apps/studio/components/interfaces/Database/Replication/ReplicationDiagram/Nodes.utils.test.ts b/apps/studio/components/interfaces/Database/Replication/ReplicationDiagram/Nodes.utils.test.ts new file mode 100644 index 00000000000..1aae574f67a --- /dev/null +++ b/apps/studio/components/interfaces/Database/Replication/ReplicationDiagram/Nodes.utils.test.ts @@ -0,0 +1,22 @@ +import { describe, expect, it } from 'vitest' + +import { getReplicationDestinationType } from './Nodes.utils' + +describe('getReplicationDestinationType', () => { + it('returns BigQuery for big_query configs', () => { + expect(getReplicationDestinationType({ big_query: {} })).toBe('BigQuery') + }) + + it('returns Analytics Bucket for iceberg configs', () => { + expect(getReplicationDestinationType({ iceberg: {} })).toBe('Analytics Bucket') + }) + + it('returns DuckLake for ducklake configs', () => { + expect(getReplicationDestinationType({ ducklake: {} })).toBe('DuckLake') + }) + + it('returns undefined for unknown or missing configs', () => { + expect(getReplicationDestinationType({})).toBeUndefined() + expect(getReplicationDestinationType(undefined)).toBeUndefined() + }) +}) diff --git a/apps/studio/components/interfaces/Database/Replication/ReplicationDiagram/Nodes.utils.ts b/apps/studio/components/interfaces/Database/Replication/ReplicationDiagram/Nodes.utils.ts new file mode 100644 index 00000000000..2135e39fcff --- /dev/null +++ b/apps/studio/components/interfaces/Database/Replication/ReplicationDiagram/Nodes.utils.ts @@ -0,0 +1,11 @@ +export type ReplicationDestinationType = 'BigQuery' | 'Analytics Bucket' | 'DuckLake' + +export const getReplicationDestinationType = ( + config?: Record +): ReplicationDestinationType | undefined => { + if (!config) return undefined + if ('big_query' in config) return 'BigQuery' + if ('iceberg' in config) return 'Analytics Bucket' + if ('ducklake' in config) return 'DuckLake' + return undefined +} diff --git a/apps/studio/components/interfaces/Database/Replication/useDestinationInformation.ts b/apps/studio/components/interfaces/Database/Replication/useDestinationInformation.ts index 746cb7d7835..828f7443125 100644 --- a/apps/studio/components/interfaces/Database/Replication/useDestinationInformation.ts +++ b/apps/studio/components/interfaces/Database/Replication/useDestinationInformation.ts @@ -2,6 +2,7 @@ import { useParams } from 'common' import { DestinationType } from './DestinationPanel/DestinationPanel.types' import { getStatusName } from './Pipeline.utils' +import { getReplicationDestinationType } from './ReplicationDiagram/Nodes.utils' import { useReplicationDestinationByIdQuery } from '@/data/replication/destination-by-id-query' import { useReplicationPipelineStatusQuery } from '@/data/replication/pipeline-status-query' import { useReplicationPipelinesQuery } from '@/data/replication/pipelines-query' @@ -26,13 +27,9 @@ export const useDestinationInformation = ({ id }: { id?: number | null }) => { projectRef, destinationId: id, }) - const destinationType: DestinationType | undefined = !destination - ? undefined - : 'big_query' in destination.config - ? 'BigQuery' - : 'iceberg' in destination.config - ? 'Analytics Bucket' - : undefined + const destinationType: DestinationType | undefined = getReplicationDestinationType( + destination?.config as Record | undefined + ) const { data: pipelinesData, diff --git a/apps/studio/components/interfaces/Database/Replication/useIsETLPrivateAlpha.ts b/apps/studio/components/interfaces/Database/Replication/useIsETLPrivateAlpha.ts index ab7cbbd391d..1c2d15922c6 100644 --- a/apps/studio/components/interfaces/Database/Replication/useIsETLPrivateAlpha.ts +++ b/apps/studio/components/interfaces/Database/Replication/useIsETLPrivateAlpha.ts @@ -31,9 +31,14 @@ export const useIsETLIcebergPrivateAlpha = () => { return useIsCurrentOrgInFlagList('etlEnableIcebergPrivateAlpha') } +export const useIsETLDucklakePrivateAlpha = () => { + return useIsCurrentOrgInFlagList('etlEnableDucklakePrivateAlpha') +} + export const useIsETLPrivateAlpha = () => { const hasAccessToETLBigQuery = useIsCurrentOrgInFlagList('etlEnableBigQueryPrivateAlpha') const hasAccessToETLIceberg = useIsCurrentOrgInFlagList('etlEnableIcebergPrivateAlpha') + const hasAccessToETLDucklake = useIsCurrentOrgInFlagList('etlEnableDucklakePrivateAlpha') - return hasAccessToETLBigQuery || hasAccessToETLIceberg + return hasAccessToETLBigQuery || hasAccessToETLIceberg || hasAccessToETLDucklake } diff --git a/apps/studio/data/replication/create-destination-pipeline-mutation.ts b/apps/studio/data/replication/create-destination-pipeline-mutation.ts index 54f5cf9cf44..1374f93730f 100644 --- a/apps/studio/data/replication/create-destination-pipeline-mutation.ts +++ b/apps/studio/data/replication/create-destination-pipeline-mutation.ts @@ -13,6 +13,9 @@ export type DestinationConfig = | { iceberg: IcebergDestinationConfig } + | { + ducklake: DucklakeDestinationConfig + } export type BigQueryDestinationConfig = { projectId: string @@ -32,6 +35,20 @@ export type IcebergDestinationConfig = { s3Region: string } +export type DucklakeDestinationConfig = { + catalogUrl: string + dataPath: string + poolSize?: number + s3AccessKeyId: string + s3SecretAccessKey: string + s3Region: string + s3Endpoint: string + s3UrlStyle?: 'path' | 'vhost' + s3UseSsl?: boolean + metadataSchema?: string + expireSnapshotsOlderThan?: string +} + export type BatchConfig = { maxFillMs?: number } @@ -108,8 +125,38 @@ async function createDestinationPipeline( }, }, } + } else if ('ducklake' in destinationConfig) { + const { + catalogUrl, + dataPath, + poolSize, + s3AccessKeyId, + s3SecretAccessKey, + s3Region, + s3Endpoint, + s3UrlStyle, + s3UseSsl, + metadataSchema, + expireSnapshotsOlderThan, + } = destinationConfig.ducklake + + destination_config = { + ducklake: { + catalog_url: catalogUrl, + data_path: dataPath, + pool_size: poolSize, + s3_access_key_id: s3AccessKeyId, + s3_secret_access_key: s3SecretAccessKey, + s3_region: s3Region, + s3_endpoint: s3Endpoint, + s3_url_style: s3UrlStyle, + s3_use_ssl: s3UseSsl, + metadata_schema: metadataSchema, + expire_snapshots_older_than: expireSnapshotsOlderThan, + }, + } as unknown as components['schemas']['CreateReplicationDestinationPipelineBody']['destination_config'] } else { - throw new Error('Invalid destination config: must specify either bigQuery or iceberg') + throw new Error('Invalid destination config: must specify bigQuery, iceberg, or ducklake') } const pipeline_config = { diff --git a/apps/studio/data/replication/update-destination-pipeline-mutation.ts b/apps/studio/data/replication/update-destination-pipeline-mutation.ts index ffe037226d0..0c70208b437 100644 --- a/apps/studio/data/replication/update-destination-pipeline-mutation.ts +++ b/apps/studio/data/replication/update-destination-pipeline-mutation.ts @@ -81,8 +81,37 @@ async function updateDestinationPipeline( }, }, } + } else if ('ducklake' in destinationConfig) { + const { + catalogUrl, + dataPath, + poolSize, + s3AccessKeyId, + s3SecretAccessKey, + s3Region, + s3Endpoint, + s3UrlStyle, + s3UseSsl, + metadataSchema, + expireSnapshotsOlderThan, + } = destinationConfig.ducklake + destination_config = { + ducklake: { + catalog_url: catalogUrl, + data_path: dataPath, + pool_size: poolSize, + s3_access_key_id: s3AccessKeyId, + s3_secret_access_key: s3SecretAccessKey, + s3_region: s3Region, + s3_endpoint: s3Endpoint, + s3_url_style: s3UrlStyle, + s3_use_ssl: s3UseSsl, + metadata_schema: metadataSchema, + expire_snapshots_older_than: expireSnapshotsOlderThan, + }, + } as unknown as components['schemas']['UpdateReplicationDestinationPipelineBody']['destination_config'] } else { - throw new Error('Invalid destination config: must specify either bigQuery or iceberg') + throw new Error('Invalid destination config: must specify bigQuery, iceberg, or ducklake') } const pipeline_config = { diff --git a/apps/studio/data/replication/validate-destination-mutation.ts b/apps/studio/data/replication/validate-destination-mutation.ts index 8668a337aa5..5ad9da83112 100644 --- a/apps/studio/data/replication/validate-destination-mutation.ts +++ b/apps/studio/data/replication/validate-destination-mutation.ts @@ -59,8 +59,38 @@ async function validateDestination( }, }, } + } else if ('ducklake' in destinationConfig) { + const { + catalogUrl, + dataPath, + poolSize, + s3AccessKeyId, + s3SecretAccessKey, + s3Region, + s3Endpoint, + s3UrlStyle, + s3UseSsl, + metadataSchema, + expireSnapshotsOlderThan, + } = destinationConfig.ducklake + + config = { + ducklake: { + catalog_url: catalogUrl, + data_path: dataPath, + pool_size: poolSize, + s3_access_key_id: s3AccessKeyId, + s3_secret_access_key: s3SecretAccessKey, + s3_region: s3Region, + s3_endpoint: s3Endpoint, + s3_url_style: s3UrlStyle, + s3_use_ssl: s3UseSsl, + metadata_schema: metadataSchema, + expire_snapshots_older_than: expireSnapshotsOlderThan, + }, + } as unknown as components['schemas']['ValidateReplicationDestinationBody']['config'] } else { - throw new Error('Invalid destination config: must specify either bigQuery or iceberg') + throw new Error('Invalid destination config: must specify bigQuery, iceberg, or ducklake') } const { data, error } = await post('/platform/replication/{ref}/destinations/validate', { From d1ec0697835c87260c4d710e88de75e915afe056 Mon Sep 17 00:00:00 2001 From: Ana <30495040+ana1337x@users.noreply.github.com> Date: Wed, 29 Apr 2026 15:54:32 -0400 Subject: [PATCH 02/21] fix: drop items-center, use text-center explicitly on centered elements (#45383) ## 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? Fix alignment ## What is the current behavior? N/A ## What is the new behavior? Align How To Enter section ## Additional context N/A ## Summary by CodeRabbit * **Style** * Adjusted the "How to enter" section layout to improve visual balance and centering across breakpoints. * Intro sentence and legal/CTA paragraph now centered for clearer emphasis. * Option and deadline blocks resized to remove forced full-width styling, yielding a cleaner, more consistent flow. --------- Co-authored-by: Ana --- .../_go/events/stripe-sessions-2026/contest.tsx | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/apps/www/_go/events/stripe-sessions-2026/contest.tsx b/apps/www/_go/events/stripe-sessions-2026/contest.tsx index 3016450f514..7bde834e6b7 100644 --- a/apps/www/_go/events/stripe-sessions-2026/contest.tsx +++ b/apps/www/_go/events/stripe-sessions-2026/contest.tsx @@ -33,10 +33,12 @@ const page: GoPageInput = { id: 'how-to-enter', title: 'How to enter', children: ( -
-

Choose either path. Both qualify.

+
+

+ Choose either path. Both qualify. +

-
+

Option 1: Via Stripe Projects

Provision a full Supabase backend from your terminal without opening a dashboard. @@ -52,7 +54,7 @@ stripe projects env --sync`}

-
+

Option 2: Via the Stripe Sync Engine

@@ -68,11 +70,11 @@ stripe projects env --sync`}
-

+

Both options must be completed by Monday, May 11, 2026 at 12:00 PM PST.

-

+

No purchase necessary. Void where prohibited.{' '} Official rules From 3be3ebb0e76867e589a4329ac21aff3208e92a6b Mon Sep 17 00:00:00 2001 From: Danny White <3104761+dnywh@users.noreply.github.com> Date: Thu, 30 Apr 2026 12:17:12 +1000 Subject: [PATCH 03/21] fix(www): partner integration links (#45391) ## What kind of change does this PR introduce? Bug fix. ## What is the current behaviour? The integrations listing links tiles to short partner URLs such as `/partners/vercel`, while the canonical integration pages live at `/partners/integrations/vercel`. Those short URLs are handled by the legacy `/partners/[slug]` route, which is meant to redirect technology partners to `/partners/integrations/[slug]`. However, that route currently pre-renders partner slugs and returns `notFound` when `npm_lifecycle_event === 'build'`, which can bake and cache static 404s for URLs that are meant to redirect. This was reportedly working as recently as last week, and the relevant partners page code has not changed recently. The most likely explanation is that old legacy-route behaviour was surfaced by a recent production build/deployment/prerender cache interaction, rather than a fresh change to the integrations tile code itself. ## What is the new behaviour? Integration tiles now link directly to the canonical `/partners/integrations/[slug]` URL, so the listing no longer depends on the legacy short-url redirect. The legacy short partner URL route also no longer pre-renders partner slugs as build-time 404s. It remains as a blocking fallback redirect for old external links, bookmarks, search results, and historical references that may still point at `/partners/[slug]`. ## Testing This URL: ``` https://zone-www-dot-com-git-dnywh-fix-partner-integrat-b3fb68-supabase.vercel.app/partners/integrations/vercel ``` ...should resolve to: ``` https://zone-www-dot-com-git-dnywh-fix-partner-integrat-b3fb68-supabase.vercel.app/partners/vercel ``` --- apps/www/components/Partners/TileGrid.tsx | 4 ++-- apps/www/pages/partners/[slug].tsx | 20 +++----------------- 2 files changed, 5 insertions(+), 19 deletions(-) diff --git a/apps/www/components/Partners/TileGrid.tsx b/apps/www/components/Partners/TileGrid.tsx index 1954d492ceb..633fa5ccac4 100644 --- a/apps/www/components/Partners/TileGrid.tsx +++ b/apps/www/components/Partners/TileGrid.tsx @@ -27,7 +27,7 @@ export default function TileGrid({

Featured

{featuredPartners?.map((p) => ( - +
{category}}
{partnersByCategory[category].map((p) => ( - +
| 67001 | ## Summary by CodeRabbit * **Bug Fixes** * Enhanced pause project confirmation dialog to clearly communicate the 90-day resume timeframe and backup availability after this period. * **Style** * Updated pause icon display. --- .../General/Infrastructure/PauseProjectButton.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/studio/components/interfaces/Settings/General/Infrastructure/PauseProjectButton.tsx b/apps/studio/components/interfaces/Settings/General/Infrastructure/PauseProjectButton.tsx index 6379f64b1f7..e48e18314f2 100644 --- a/apps/studio/components/interfaces/Settings/General/Infrastructure/PauseProjectButton.tsx +++ b/apps/studio/components/interfaces/Settings/General/Infrastructure/PauseProjectButton.tsx @@ -1,5 +1,5 @@ import { PermissionAction } from '@supabase/shared-types/out/constants' -import { Pause } from 'lucide-react' +import { CirclePause } from 'lucide-react' import { useRouter } from 'next/router' import { useState } from 'react' import { toast } from 'sonner' @@ -86,7 +86,7 @@ const PauseProjectButton = () => { <> } + icon={} onClick={() => setIsModalOpen(true)} loading={isPausing} disabled={buttonDisabled} @@ -105,8 +105,8 @@ const PauseProjectButton = () => { Pause project? - This project will be unavailable while paused. Your data stays safe, and you can - resume it later from the dashboard while it remains within the restore window. + This project will be unavailable while paused. Paused projects can be resumed for 90 + days. After that, backups remain available to download. From 3230cbd4c37756656a6bf1ddf32af84a1fa3f59b Mon Sep 17 00:00:00 2001 From: Chris Chinchilla Date: Thu, 30 Apr 2026 08:23:19 +0200 Subject: [PATCH 05/21] docs: Change Pico terminology (#45053) ## I have read the [CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md) file. YES ## Summary by CodeRabbit * **Documentation** * Updated compute instance sizing guidance to clarify that scale-to-zero pricing applies to Nano instances * Refreshed documentation sections describing compute instance options and pricing tiers * Removed outdated information about Pico instance capabilities --------- Co-authored-by: Wen Bo Xie --- .../integrations/supabase-for-platforms.mdx | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/apps/docs/content/guides/integrations/supabase-for-platforms.mdx b/apps/docs/content/guides/integrations/supabase-for-platforms.mdx index f2950894cf0..e1204f22222 100644 --- a/apps/docs/content/guides/integrations/supabase-for-platforms.mdx +++ b/apps/docs/content/guides/integrations/supabase-for-platforms.mdx @@ -28,7 +28,7 @@ We recommend: - **a _very_ secure password for each database**. Do not reuse the same password across databases. - **storing the encrypted version of the password**. Once you set the password during project creation, there is no way to programmatically change the password but you can do so manually in the Supabase Dashboard. - **using smart region selection to ensure there's enough capacity**. The available smart region codes are `americas`, `emea`, and `apac` and you can make a request to [`GET /v1/projects/available-regions`](https://api.supabase.com/api/v1#tag/projects/get/v1/projects/available-regions) for region details. -- **using an appropriate instance size**. Scale to zero pricing only applies to `pico` instances, make sure to not pass in a `desired_instance_size` when creating a project. >= Micro instances are not able to scale to zero. +- **using an appropriate instance size**. Scale to zero pricing only applies to Nano instances, make sure to not pass in a `desired_instance_size` when creating a project. >= Micro instances are not able to scale to zero. - **make sure that the services are `ACTIVE_HEALTHY` after project creation**. After creating project, confirm the service that you want to make a request to has a status of `ACTIVE_HEALTHY` by polling [`GET /v1/projects/{ref}/health`](https://api.supabase.com/api/v1#tag/projects/get/v1/projects/{ref}/health). For example, before making a request to set an Auth configuration confirm that the Auth service has a status of `ACTIVE_HEALTHY`. ```sh @@ -48,16 +48,14 @@ curl https://api.supabase.com/v1/projects \ }' ``` -### Pico compute instance +### Nano compute instance - + -Only select customers have access to Pico instances. Submit this [form](/solutions/ai-builders#talk-to-partnerships-team) to get access. +Only select customers have access to scale to zero pricing on Nano instances. Submit this [form](/solutions/ai-builders#talk-to-partnerships-team) to get access. -Pico instance is our newest compute instance and the only one that scales to zero when not used. - ### Recommended API keys Management API endpoints: @@ -113,12 +111,6 @@ curl 'https://api.supabase.com/v1/projects/{ref}/api-keys?reveal=true' \ Management API endpoint: [`PATCH /v1/projects/{ref}/billing/addons`](https://api.supabase.com/api/v1#tag/billing/patch/v1/projects/{ref}/billing/addons) - - -Once you have access to [Pico instances](#pico-compute-instance), you will no longer be able to upgrade or downgrade to Nano instances. - - - You can upgrade and downgrade compute sizes by making requests to [`PATCH /v1/projects/{ref}/billing/addons`](https://api.supabase.com/api/v1#tag/billing/patch/v1/projects/{ref}/billing/addons). ```sh From d0af257c2b6bfa022e8535506d759ca64f9de2d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harold=20Gim=C3=A9nez?= Date: Thu, 30 Apr 2026 02:25:46 -0400 Subject: [PATCH 06/21] =?UTF-8?q?Add=20Harold=20Gim=C3=A9nez=20to=20humans?= =?UTF-8?q?.txt=20(#45387)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 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? add myself to humans.txt ## What is the current behavior? doesn't have my name ## What is the new behavior? has my name ## Additional context just onboarding! ## Summary by CodeRabbit * **Chores** * Updated documentation contributors list. --- apps/docs/public/humans.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/docs/public/humans.txt b/apps/docs/public/humans.txt index ee34610d312..088c1c96703 100644 --- a/apps/docs/public/humans.txt +++ b/apps/docs/public/humans.txt @@ -105,6 +105,7 @@ Guilherme Souza Hannah Bowers Hardik Maheshwari Haritabh Gupta +Harold Giménez Haydn Maley Hieu Pham Hunter Luckow From 1293282d209e2d0ed4b94b489b08c81fa893f742 Mon Sep 17 00:00:00 2001 From: Manuel Mendez <708570+mmlb@users.noreply.github.com> Date: Thu, 30 Apr 2026 02:26:05 -0400 Subject: [PATCH 07/21] Add Manuel Mendez to humans.txt (#45346) ## 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? docs update ## What is the current behavior? I am not in the docs ## What is the new behavior? I am in the docs ## Summary by CodeRabbit * **Chores** * Updated credits list to include a new contributor. --- apps/docs/public/humans.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/docs/public/humans.txt b/apps/docs/public/humans.txt index 088c1c96703..aa76e218b23 100644 --- a/apps/docs/public/humans.txt +++ b/apps/docs/public/humans.txt @@ -166,6 +166,7 @@ Long Hoang Lukas Bernert Łukasz Niemier Maksym Ionutsa +Manuel Mendez Manan Gupta Marcus Weiner Margarita Sandomirskaia From 5dee242aa3bd59ece5d1c471bb8e70bd26473c5d Mon Sep 17 00:00:00 2001 From: Danny White <3104761+dnywh@users.noreply.github.com> Date: Thu, 30 Apr 2026 16:48:51 +1000 Subject: [PATCH 08/21] chore(studio): remove project usage settings callout (#45393) ## What kind of change does this PR introduce? Dashboard cleanup and docs update. ## What is the current behaviour? Project Settings > General still shows a legacy "Project usage" section explaining that usage statistics moved to organisation settings. One troubleshooting page also links to the old project billing usage page. ## What is the new behaviour? The legacy Project Settings usage callout is removed, while the existing old usage route redirect remains in place for stale links. The MAU troubleshooting page now points users to the organisation usage page and tells them to select a specific project from the dropdown. | Before | After | | --- | --- | | CleanShot 2026-04-30 at 16 11
16@2x | CleanShot 2026-04-30 at 16 11
04@2x-7CACB175-B6A9-4811-968F-030745F685AE | ## Summary by CodeRabbit * **Documentation** * Updated Monthly Active Users (MAU) documentation to reflect accessing usage data from the organization-level page instead of project settings * **Refactor** * Removed project-level usage viewing option from project settings interface --- ...ge-for-monthly-active-users-mau-MwZaBs.mdx | 2 +- .../Settings/General/Project.test.tsx | 25 +++--------- .../interfaces/Settings/General/Project.tsx | 39 ------------------- 3 files changed, 7 insertions(+), 59 deletions(-) diff --git a/apps/docs/content/troubleshooting/check-usage-for-monthly-active-users-mau-MwZaBs.mdx b/apps/docs/content/troubleshooting/check-usage-for-monthly-active-users-mau-MwZaBs.mdx index 3f1f45e1d70..cfec63c4d4f 100644 --- a/apps/docs/content/troubleshooting/check-usage-for-monthly-active-users-mau-MwZaBs.mdx +++ b/apps/docs/content/troubleshooting/check-usage-for-monthly-active-users-mau-MwZaBs.mdx @@ -7,7 +7,7 @@ keywords = [ "MAU", "Monthly active users" ] database_id = "1759134d-a98d-4bfa-9501-71e06180700e" --- -You can see the usage in your [projects usage page](https://app.supabase.com/project/_/settings/billing/usage). +You can view Monthly Active Users usage on the [organisation's usage page](/dashboard/org/_/usage). To view usage for a specific project, select it from the project dropdown. For MAU, we rely on the [Auth Server](https://github.com/supabase/auth) logs. MAU count is relative to your billing cycle and resets whenever your billing cycle resets. You can check your Auth logs in your [projects logs & analytics section](/dashboard/project/_/logs/auth-logs). diff --git a/apps/studio/components/interfaces/Settings/General/Project.test.tsx b/apps/studio/components/interfaces/Settings/General/Project.test.tsx index 4f2c909fbda..eeb3ead7ee7 100644 --- a/apps/studio/components/interfaces/Settings/General/Project.test.tsx +++ b/apps/studio/components/interfaces/Settings/General/Project.test.tsx @@ -4,17 +4,12 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' import { Project } from './Project' -const { - mockUseIsFeatureEnabled, - mockUseProjectPauseStatusQuery, - mockUseSelectedOrganizationQuery, - mockUseSelectedProjectQuery, -} = vi.hoisted(() => ({ - mockUseIsFeatureEnabled: vi.fn(), - mockUseProjectPauseStatusQuery: vi.fn(), - mockUseSelectedOrganizationQuery: vi.fn(), - mockUseSelectedProjectQuery: vi.fn(), -})) +const { mockUseIsFeatureEnabled, mockUseProjectPauseStatusQuery, mockUseSelectedProjectQuery } = + vi.hoisted(() => ({ + mockUseIsFeatureEnabled: vi.fn(), + mockUseProjectPauseStatusQuery: vi.fn(), + mockUseSelectedProjectQuery: vi.fn(), + })) vi.mock('next/link', () => ({ default: ({ href, children }: { href: string; children: ReactNode }) => ( @@ -68,10 +63,6 @@ vi.mock('@/hooks/misc/useIsFeatureEnabled', () => ({ useIsFeatureEnabled: mockUseIsFeatureEnabled, })) -vi.mock('@/hooks/misc/useSelectedOrganization', () => ({ - useSelectedOrganizationQuery: mockUseSelectedOrganizationQuery, -})) - vi.mock('@/hooks/misc/useSelectedProject', () => ({ useSelectedProjectQuery: mockUseSelectedProjectQuery, })) @@ -84,10 +75,6 @@ describe('Project settings availability', () => { projectSettingsRestartProject: true, }) - mockUseSelectedOrganizationQuery.mockReturnValue({ - data: { slug: 'supabase' }, - }) - mockUseProjectPauseStatusQuery.mockReturnValue({ data: undefined, isError: false, diff --git a/apps/studio/components/interfaces/Settings/General/Project.tsx b/apps/studio/components/interfaces/Settings/General/Project.tsx index 80d94d6352f..28ed752273f 100644 --- a/apps/studio/components/interfaces/Settings/General/Project.tsx +++ b/apps/studio/components/interfaces/Settings/General/Project.tsx @@ -1,4 +1,3 @@ -import { BarChart2 } from 'lucide-react' import Link from 'next/link' import { Button, Card, CardContent } from 'ui' import { @@ -15,14 +14,11 @@ import RestartServerButton from './Infrastructure/RestartServerButton' import { ResumeProjectButton } from '@/components/interfaces/Project/ResumeProjectButton' import { useProjectPauseStatusQuery } from '@/data/projects/project-pause-status-query' import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled' -import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization' import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' import { PROJECT_STATUS } from '@/lib/constants' export const Project = () => { const { data: project } = useSelectedProjectQuery() - const { data: organization } = useSelectedOrganizationQuery() - const isBranch = Boolean(project?.parent_project_ref) const isPaused = project?.status === PROJECT_STATUS.INACTIVE const { projectSettingsRestartProject } = useIsFeatureEnabled([ 'project_settings:restart_project', @@ -109,41 +105,6 @@ export const Project = () => { - - {!isBranch && ( - - - - Project usage - - - - - -
-
- -
-

Project usage statistics have moved

-

- Project usage is now viewable within organization settings. -

-
-
- - {!!organization && !!project && ( - - )} -
-
-
-
-
- )} ) } From 3ef1c1e08cd7e98bb076672b52a9304659c1944a Mon Sep 17 00:00:00 2001 From: Saxon Fletcher Date: Thu, 30 Apr 2026 18:09:33 +1000 Subject: [PATCH 09/21] Add recommendation on schema isolation (#45390) --- apps/docs/content/guides/api/securing-your-api.mdx | 4 ++++ .../components/interfaces/Settings/API/HardenAPIModal.tsx | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/docs/content/guides/api/securing-your-api.mdx b/apps/docs/content/guides/api/securing-your-api.mdx index ba064520884..de875e9e599 100644 --- a/apps/docs/content/guides/api/securing-your-api.mdx +++ b/apps/docs/content/guides/api/securing-your-api.mdx @@ -68,6 +68,10 @@ alter default privileges for role postgres in schema public revoke execute on functions from public; ``` +## Use a dedicated API schema + +If you want an extra boundary around your Data API, lock down the `public` schema and expose a dedicated schema, such as `api`, instead. You can control access with grants in any schema, but this can make the surface easier to reason about: objects in `api` represent your Data API, while internal tables and helper functions stay in schemas that are not exposed. See [Using Custom Schemas](/docs/guides/api/using-custom-schemas) for setup steps. + ## Disable the Data API If your app never uses Supabase client libraries, REST, or GraphQL data endpoints, turn the Data API off: diff --git a/apps/studio/components/interfaces/Settings/API/HardenAPIModal.tsx b/apps/studio/components/interfaces/Settings/API/HardenAPIModal.tsx index 8af67b6b776..46332736e5a 100644 --- a/apps/studio/components/interfaces/Settings/API/HardenAPIModal.tsx +++ b/apps/studio/components/interfaces/Settings/API/HardenAPIModal.tsx @@ -123,7 +123,7 @@ export const HardenAPIModal = ({ visible, onClose }: HardenAPIModalProps) => { From 16db81cc9c60abf68fc5a613ee2af6acfe6fdd70 Mon Sep 17 00:00:00 2001 From: Danny White <3104761+dnywh@users.noreply.github.com> Date: Thu, 30 Apr 2026 18:31:45 +1000 Subject: [PATCH 10/21] chore(studio): clarify Stripe Projects connect flow (#44797) ## What kind of change does this PR introduce? Studio UI copy and visual polish for the Stripe Projects connect flow. - Resolves DEPR-428 - Resolves DEPR-429 ## What is the current behaviour? - The Stripe Projects connect flow copy is awkward in request, linked, wrong-account, and success states. - The API authorisation layout logo is slightly misaligned. - The bundled Stripe icon is outdated. ## What is the new behaviour? - Tightens Stripe Projects copy and CTA text around authorising the request. - Keeps the real `ar_id` account-request flow intact without local preview shortcuts. - Centres the logo link in `APIAuthorizationLayout`. - Updates the Stripe icon SVG. - Note that this affects other areas too, such as Stripe integrations ([more](https://supabase.slack.com/archives/C0429V78ACX/p1777268209661729)). - Fixes CLI login copy capitalisation. | Before | After | | --- | --- | | CleanShot 2026-04-29 at 15 10
26@2x | CleanShot 2026-04-29 at 15 10
36@2x | ## Verification - `pnpm exec eslint pages/partners/stripe/projects/login.tsx` - `git diff --check` --------- Co-authored-by: Claude Sonnet 4.6 Co-authored-by: Ivan Vasilov --- .../layouts/APIAuthorizationLayout.tsx | 2 +- apps/studio/pages/cli/login.tsx | 2 +- .../pages/partners/stripe/projects/login.tsx | 125 ++++++++++-------- apps/studio/public/img/icons/stripe-icon.svg | 2 +- 4 files changed, 72 insertions(+), 59 deletions(-) diff --git a/apps/studio/components/layouts/APIAuthorizationLayout.tsx b/apps/studio/components/layouts/APIAuthorizationLayout.tsx index 7a10bc7227a..cbd890004c0 100644 --- a/apps/studio/components/layouts/APIAuthorizationLayout.tsx +++ b/apps/studio/components/layouts/APIAuthorizationLayout.tsx @@ -30,7 +30,7 @@ export const APIAuthorizationLayout = ({
- + Supabase {
{device_code ? ( <> -

Your Supabase Account is being used to login on Supabase CLI.

+

Your Supabase account is being used to login on Supabase CLI

Enter this verification code on Supabase CLI to authorize login.

diff --git a/apps/studio/pages/partners/stripe/projects/login.tsx b/apps/studio/pages/partners/stripe/projects/login.tsx index a2099a1a19e..80b7862fc71 100644 --- a/apps/studio/pages/partners/stripe/projects/login.tsx +++ b/apps/studio/pages/partners/stripe/projects/login.tsx @@ -3,20 +3,25 @@ import { useParams } from 'common' import Head from 'next/head' import { useRouter } from 'next/router' import { useEffect } from 'react' -import { - Alert_Shadcn_, - AlertDescription_Shadcn_, - AlertTitle_Shadcn_, - Button, - LogoLoader, - WarningIcon, -} from 'ui' +import { Button, LogoLoader } from 'ui' +import { Admonition } from 'ui-patterns' import { APIAuthorizationLayout } from '@/components/layouts/APIAuthorizationLayout' import { useConfirmAccountRequestMutation } from '@/data/partners/stripe-projects-confirm-mutation' import { accountRequestQueryOptions } from '@/data/partners/stripe-projects-query' import { withAuth } from '@/hooks/misc/withAuth' import { useSignOut } from '@/lib/auth' +import { BASE_PATH } from '@/lib/constants' + +const StripeIcon = () => ( + Stripe +) const StripeProjectsLoginPage = () => { const router = useRouter() @@ -26,16 +31,19 @@ const StripeProjectsLoginPage = () => { const { data: accountRequest, - isPending, - isSuccess, - isError, + isPending: isQueryPending, + isSuccess: isQuerySuccess, + isError: isQueryError, error, - } = useQuery(accountRequestQueryOptions({ arId: ar_id })) + } = useQuery({ + ...accountRequestQueryOptions({ arId: ar_id }), + enabled: typeof ar_id !== 'undefined', + }) const { mutate: confirmAccountRequest, - isPending: isConfirming, - isSuccess: isConfirmed, + isPending: isConfirmationPending, + isSuccess: isConfirmationSuccess, } = useConfirmAccountRequestMutation() useEffect(() => { @@ -48,22 +56,28 @@ const StripeProjectsLoginPage = () => { }, [router.isReady, ar_id, router]) const handleApprove = async () => { - if (!ar_id || isConfirming) return + if (!ar_id || isConfirmationPending) return confirmAccountRequest({ arId: ar_id }) } + const isPending = isQueryPending + const isSuccess = isQuerySuccess + const isConfirmed = isConfirmationSuccess + const isConfirming = isConfirmationPending + const isError = isQueryError + const linkedOrg = accountRequest?.linked_organization const emailMatches = accountRequest?.email_matches ?? false - const loadingText = linkedOrg ? 'Completing authorization...' : 'Creating your organization...' - const successTitle = linkedOrg ? 'Authorization Complete' : 'Organization Created' + const loadingText = linkedOrg ? 'Authorizing...' : 'Creating organization...' + const successTitle = linkedOrg ? 'Authorized' : 'Organization created' const successDescription = linkedOrg ? null : 'Your Supabase organization has been created and linked to your Stripe account.' return ( -
+
{isConfirming ? ( <> @@ -71,32 +85,39 @@ const StripeProjectsLoginPage = () => { ) : isConfirmed ? ( <> +

{successTitle}

- {successDescription &&

{successDescription}

} -

You can close this window.

+

+ {successDescription && `${successDescription} `} + You can close this window. +

) : isPending ? ( ) : isSuccess ? ( <> -

Stripe Account Authorization

-

- Stripe wants to link a Supabase organization for{' '} - {accountRequest.email}. + +

+ Stripe Projects is requesting access +

+

+ Stripe Projects wants to connect to the Supabase account for{' '} + {accountRequest?.email}. + {emailMatches && !linkedOrg && ( + <> This will create a new Supabase organization linked to Stripe. + )}

- {!emailMatches ? ( <> - - - Wrong account - - You need to be logged in as {accountRequest.email} to approve - this request. - - + +

+ You're signed in as a different account. Sign out and sign back in as{' '} + {accountRequest?.email}. Then + return to Stripe to restart the request. +

+
-
@@ -104,38 +125,30 @@ const StripeProjectsLoginPage = () => { ) : linkedOrg ? ( // Org already linked to this Stripe account — inform user and confirm <> -

- Your organization {linkedOrg.name} is already linked to your - Stripe account. -

+ +

+ {linkedOrg.name} is already linked + to Stripe. Authorize Stripe Projects to continue. +

+
) : ( // No linked org — a new one will be created - <> -

- A new Supabase organization will be created and linked to your Stripe account. -

-
- -
- +
+ +
)} ) : isError ? ( @@ -143,7 +156,7 @@ const StripeProjectsLoginPage = () => {

Error

{error?.message}

-
diff --git a/apps/studio/public/img/icons/stripe-icon.svg b/apps/studio/public/img/icons/stripe-icon.svg index 66dd20f76c0..39d3ae109eb 100644 --- a/apps/studio/public/img/icons/stripe-icon.svg +++ b/apps/studio/public/img/icons/stripe-icon.svg @@ -1 +1 @@ - \ No newline at end of file + From f4abe3fca74797da040db68e9e3b8ef7d09291fd Mon Sep 17 00:00:00 2001 From: Gildas Garcia <1122076+djhi@users.noreply.github.com> Date: Thu, 30 Apr 2026 10:35:01 +0200 Subject: [PATCH 11/21] chore: migrate `MultiSelectDeprecated` to Shadcn `multi-select` (#45377) ## Problem We want to reduce the code we ship and maintain. ## Solution - Migrate old `MultiSelectDeprecated` usage to the new Shadcn `multi-select` - Fix `multi-select` background color to align it with other inputs - Fix `multi-select` popover content alignment (now align to its input start) ## Screenshots ### RLS policies Before: image After: image ### Storage policies Before: image After: image ### Database indexes Before: image After: image ### Contact support Before: image After: image ## Summary by CodeRabbit * **Refactor** * Replaced legacy multi-select controls with a unified selector UI: dynamic trigger labels, per-item disable support, explicit item rendering, deletable badges, and improved search/selection behavior. * **Chores** * Removed deprecated multi-select badge and legacy picker implementations; adjusted exports/types to align with the new selector components. * **Style** * Minor UI text and inline code styling improvements and modal spacing tweaks. * **Tests** * Updated end-to-end flows to wait and interact with the new pickers. --------- Co-authored-by: Joshen Lim --- .../Policies/PolicyEditor/PolicyRoles.tsx | 37 ++- .../PolicyEditorPanel/PolicyDetailsV2.tsx | 49 +++- .../Policies/PolicyTableRow/PolicyRow.tsx | 23 +- .../Database/Indexes/CreateIndexSidePanel.tsx | 47 ++- .../interfaces/Database/Indexes/Indexes.tsx | 8 +- .../Support/AffectedServicesSelector.tsx | 40 ++- .../pages/project/[ref]/database/indexes.tsx | 4 +- e2e/studio/features/database.spec.ts | 18 +- e2e/studio/features/rls-policies.spec.ts | 8 +- .../src/MultiSelectDeprecated/Badges.tsx | 47 --- .../MultiSelectDeprecated/MultiSelectV2.tsx | 159 ---------- .../src/MultiSelectDeprecated/index.tsx | 274 ------------------ .../src/multi-select/multi-select.tsx | 21 +- packages/ui/index.tsx | 1 + .../ui/src/components/shadcn/ui/popover.tsx | 2 +- 15 files changed, 195 insertions(+), 543 deletions(-) delete mode 100644 packages/ui-patterns/src/MultiSelectDeprecated/Badges.tsx delete mode 100644 packages/ui-patterns/src/MultiSelectDeprecated/MultiSelectV2.tsx delete mode 100644 packages/ui-patterns/src/MultiSelectDeprecated/index.tsx diff --git a/apps/studio/components/interfaces/Auth/Policies/PolicyEditor/PolicyRoles.tsx b/apps/studio/components/interfaces/Auth/Policies/PolicyEditor/PolicyRoles.tsx index e8b266d5314..c0e57e7bc6f 100644 --- a/apps/studio/components/interfaces/Auth/Policies/PolicyEditor/PolicyRoles.tsx +++ b/apps/studio/components/interfaces/Auth/Policies/PolicyEditor/PolicyRoles.tsx @@ -1,5 +1,11 @@ import { sortBy } from 'lodash' -import MultiSelect from 'ui-patterns/MultiSelectDeprecated' +import { + MultiSelector, + MultiSelectorContent, + MultiSelectorItem, + MultiSelectorList, + MultiSelectorTrigger, +} from 'ui-patterns/multi-select' import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader' import { SYSTEM_ROLES } from '@/components/interfaces/Database/Roles/Roles.constants' @@ -51,13 +57,28 @@ export const PolicyRoles = ({ selectedRoles, onUpdateSelectedRoles }: PolicyRole {isLoading && } {isError && } {isSuccess && ( - + + + + + {formattedRoles.map((role) => ( + + {role.name} + + ))} + + + )}
diff --git a/apps/studio/components/interfaces/Auth/Policies/PolicyEditorPanel/PolicyDetailsV2.tsx b/apps/studio/components/interfaces/Auth/Policies/PolicyEditorPanel/PolicyDetailsV2.tsx index d132e7a304e..1de3817ad99 100644 --- a/apps/studio/components/interfaces/Auth/Policies/PolicyEditorPanel/PolicyDetailsV2.tsx +++ b/apps/studio/components/interfaces/Auth/Policies/PolicyEditorPanel/PolicyDetailsV2.tsx @@ -29,7 +29,13 @@ import { SelectItem_Shadcn_, SelectTrigger_Shadcn_, } from 'ui' -import { MultiSelectV2 } from 'ui-patterns/MultiSelectDeprecated/MultiSelectV2' +import { + MultiSelector, + MultiSelectorContent, + MultiSelectorItem, + MultiSelectorList, + MultiSelectorTrigger, +} from 'ui-patterns/multi-select' import { useDatabaseRolesQuery } from '@/data/database-roles/database-roles-query' import { useTablesQuery } from '@/data/tables/tables-query' @@ -298,18 +304,43 @@ export const PolicyDetailsV2 = ({ name="roles" render={({ field }) => ( - + Target Roles to clause - field.onChange(roles.join(', '))} disabled={!canUpdatePolicies} - options={formattedRoles} - value={field.value.length === 0 ? [] : field.value?.split(', ')} - placeholder="Defaults to all (public) roles if none selected" - searchPlaceholder="Search for a role" - onChange={(roles) => form.setValue('roles', roles.join(', '))} - /> + values={field.value.length === 0 ? [] : field.value?.split(', ')} + size="small" + > + + + + {formattedRoles.map((role) => ( + + {role.name} + + ))} + + + diff --git a/apps/studio/components/interfaces/Auth/Policies/PolicyTableRow/PolicyRow.tsx b/apps/studio/components/interfaces/Auth/Policies/PolicyTableRow/PolicyRow.tsx index 91a78d00b30..7972413c508 100644 --- a/apps/studio/components/interfaces/Auth/Policies/PolicyTableRow/PolicyRow.tsx +++ b/apps/studio/components/interfaces/Auth/Policies/PolicyTableRow/PolicyRow.tsx @@ -79,14 +79,14 @@ export const PolicyRow = ({
- {policy.command} + {policy.command}
{displayedRoles.slice(0, 2).map((role, i) => ( - {role} + {role} {i < Math.min(displayedRoles.length, 2) - 1 ? ', ' : ' '} ))} @@ -95,13 +95,24 @@ export const PolicyRow = ({
- + + {displayedRoles.length - 2} more - +
- - {displayedRoles.join(', ')} + + {displayedRoles.slice(2).map((role, i, arr) => ( + <> + + {role} + + {i < arr.length - 1 && ', '} + + ))}
)} diff --git a/apps/studio/components/interfaces/Database/Indexes/CreateIndexSidePanel.tsx b/apps/studio/components/interfaces/Database/Indexes/CreateIndexSidePanel.tsx index 5236fe55014..40d6174a1b0 100644 --- a/apps/studio/components/interfaces/Database/Indexes/CreateIndexSidePanel.tsx +++ b/apps/studio/components/interfaces/Database/Indexes/CreateIndexSidePanel.tsx @@ -24,8 +24,13 @@ import { } from 'ui' import { Admonition } from 'ui-patterns' import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' -import { MultiSelectOption } from 'ui-patterns/MultiSelectDeprecated' -import { MultiSelectV2 } from 'ui-patterns/MultiSelectDeprecated/MultiSelectV2' +import { + MultiSelector, + MultiSelectorContent, + MultiSelectorItem, + MultiSelectorList, + MultiSelectorTrigger, +} from 'ui-patterns/multi-select' import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader' import { INDEX_TYPES } from './Indexes.constants' @@ -101,7 +106,7 @@ export const CreateIndexSidePanel = ({ visible, onClose }: CreateIndexSidePanelP } const columns = tableColumns?.[0]?.columns ?? [] - const columnOptions: MultiSelectOption[] = columns + const columnOptions = columns .filter((column): column is NonNullable => column !== null) .map((column) => ({ id: column.attname, @@ -328,16 +333,36 @@ CREATE INDEX ON "${selectedSchema}"."${selectedEntity}" USING ${selectedIndexTyp {selectedEntity && ( - + {isLoadingTableColumns && } {isSuccessTableColumns && ( - + + + + + {columnOptions.map((option) => ( + + {option.name} + + ))} + + + )} )} diff --git a/apps/studio/components/interfaces/Database/Indexes/Indexes.tsx b/apps/studio/components/interfaces/Database/Indexes/Indexes.tsx index 266b3f879c3..85160122e0a 100644 --- a/apps/studio/components/interfaces/Database/Indexes/Indexes.tsx +++ b/apps/studio/components/interfaces/Database/Indexes/Indexes.tsx @@ -31,7 +31,7 @@ import { useQuerySchemaState } from '@/hooks/misc/useSchemaQueryState' import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' import { useIsProtectedSchema } from '@/hooks/useProtectedSchemas' -const Indexes = () => { +export const Indexes = () => { const { data: project } = useSelectedProjectQuery() const { schema: urlSchema, table } = useParams() @@ -278,7 +278,8 @@ const Indexes = () => { visible={!!selectedIndexToDelete} title={ <> - Confirm to delete index {selectedIndexToDelete?.name} + Confirm to delete index{' '} + {selectedIndexToDelete?.name} } confirmLabel="Confirm delete" @@ -292,6 +293,7 @@ const Indexes = () => { description: 'Deleting an index that is still in use will cause queries to slow down, and in some cases causing significant performance issues.', }} + className="pt-0" >
  • @@ -311,5 +313,3 @@ const Indexes = () => { ) } - -export default Indexes diff --git a/apps/studio/components/interfaces/Support/AffectedServicesSelector.tsx b/apps/studio/components/interfaces/Support/AffectedServicesSelector.tsx index 7ca04998d9e..43879bb4fe5 100644 --- a/apps/studio/components/interfaces/Support/AffectedServicesSelector.tsx +++ b/apps/studio/components/interfaces/Support/AffectedServicesSelector.tsx @@ -4,7 +4,13 @@ import { SupportCategories } from '@supabase/shared-types/out/constants' import type { UseFormReturn } from 'react-hook-form' import { FormControl, FormField } from 'ui' import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' -import { MultiSelectV2 } from 'ui-patterns/MultiSelectDeprecated/MultiSelectV2' +import { + MultiSelector, + MultiSelectorContent, + MultiSelectorItem, + MultiSelectorList, + MultiSelectorTrigger, +} from 'ui-patterns/multi-select' import { SERVICE_OPTIONS, type ExtendedSupportCategories } from './Support.constants' import type { SupportFormValues } from './SupportForm.schema' @@ -29,13 +35,31 @@ export function AffectedServicesSelector({ form, category }: AffectedServicesSel render={({ field }) => ( - form.setValue('affectedServices', services.join(', '))} - /> + field.onChange(services.join(', '))} + > + + + + {SERVICE_OPTIONS.map((service) => ( + + {service.name} + + ))} + + + )} diff --git a/apps/studio/pages/project/[ref]/database/indexes.tsx b/apps/studio/pages/project/[ref]/database/indexes.tsx index 858b99dfcdc..f9f558a88da 100644 --- a/apps/studio/pages/project/[ref]/database/indexes.tsx +++ b/apps/studio/pages/project/[ref]/database/indexes.tsx @@ -11,9 +11,9 @@ import { } from 'ui-patterns/PageHeader' import { PageSection, PageSectionContent } from 'ui-patterns/PageSection' -import Indexes from '@/components/interfaces/Database/Indexes/Indexes' +import { Indexes } from '@/components/interfaces/Database/Indexes/Indexes' import DatabaseLayout from '@/components/layouts/DatabaseLayout/DatabaseLayout' -import DefaultLayout from '@/components/layouts/DefaultLayout' +import { DefaultLayout } from '@/components/layouts/DefaultLayout' import { DocsButton } from '@/components/ui/DocsButton' import { DOCS_URL } from '@/lib/constants' import type { NextPageWithLayout } from '@/types' diff --git a/e2e/studio/features/database.spec.ts b/e2e/studio/features/database.spec.ts index 7cd1d8a1f54..80cc3a6520e 100644 --- a/e2e/studio/features/database.spec.ts +++ b/e2e/studio/features/database.spec.ts @@ -651,16 +651,26 @@ test.describe('Database', () => { await dropTable(databaseTableName) } ) + const indexWait = waitForApiResponse(page, 'pg-meta', ref, 'query?key=indexes-public') await page.goto(toUrl(`/project/${env.PROJECT_REF}/database/indexes?schema=public`)) // Wait for database indexes to be populated - await waitForApiResponse(page, 'pg-meta', ref, 'query?key=indexes-public') + await indexWait // create new index await page.getByRole('button', { name: 'Create index' }).click() await page.getByRole('button', { name: 'Choose a table' }).click() + + const columnsWait = waitForApiResponse( + page, + 'pg-meta', + ref, + `query?key=table-columns-public-${databaseTableName}` + ) await page.getByRole('option', { name: databaseTableName, exact: true }).click() - await page.getByText('Choose which columns to create an index on').click() + await columnsWait + + await page.getByRole('combobox', { name: 'Select up to 32 columns' }).click() await page.getByRole('option', { name: databaseColumnName }).click() await page.getByRole('button', { name: 'Create index' }).click() await expect( @@ -1136,9 +1146,7 @@ test.describe('Database Enumerated Types', () => { await quotedEnumCreateWait const quotedEnumRow = page.getByRole('row', { name: `${quotedEnumName}` }) await expect(quotedEnumRow).toContainText(quotedEnumName) - await expect(quotedEnumRow).toContainText( - `${quotedEnumValue1Name}, ${quotedEnumValue2Name}` - ) + await expect(quotedEnumRow).toContainText(`${quotedEnumValue1Name}, ${quotedEnumValue2Name}`) await quotedEnumRow.getByRole('button').click() await page.getByRole('menuitem', { name: 'Update type' }).click() diff --git a/e2e/studio/features/rls-policies.spec.ts b/e2e/studio/features/rls-policies.spec.ts index ac615c4a382..b6a3cabd178 100644 --- a/e2e/studio/features/rls-policies.spec.ts +++ b/e2e/studio/features/rls-policies.spec.ts @@ -1,9 +1,9 @@ import { expect, Page } from '@playwright/test' +import { createTableWithRLS, dropTable } from '../utils/db/queries.js' import { test, withSetupCleanup } from '../utils/test.js' import { toUrl } from '../utils/to-url.js' import { createApiResponseWaiter, waitForApiResponse } from '../utils/wait-for-response.js' -import { createTableWithRLS, dropTable } from '../utils/db/queries.js' /** * Helper function to navigate to policies page and wait for it to load @@ -283,7 +283,7 @@ test.describe('RLS Policies', () => { await page.getByRole('radio', { name: 'INSERT' }).click() // Select target role - authenticated - await page.getByText('Defaults to all (public) roles if none selected').click() + await page.getByRole('combobox', { name: 'Target Roles' }).click() await page.getByRole('option', { name: 'authenticated' }).click() // Close the dropdown @@ -338,7 +338,7 @@ test.describe('RLS Policies', () => { await page.getByRole('radio', { name: 'UPDATE' }).click() // Select authenticated role - await page.getByText('Defaults to all (public) roles if none selected').click() + await page.getByRole('combobox', { name: 'Target Roles' }).click() await page.getByRole('option', { name: 'authenticated' }).click() await page.keyboard.press('Escape') @@ -390,7 +390,7 @@ test.describe('RLS Policies', () => { await page.getByRole('radio', { name: 'DELETE' }).click() // Select authenticated role - await page.getByText('Defaults to all (public) roles if none selected').click() + await page.getByRole('combobox', { name: 'Target Roles' }).click() await page.getByRole('option', { name: 'authenticated' }).click() await page.keyboard.press('Escape') diff --git a/packages/ui-patterns/src/MultiSelectDeprecated/Badges.tsx b/packages/ui-patterns/src/MultiSelectDeprecated/Badges.tsx deleted file mode 100644 index e3e82002087..00000000000 --- a/packages/ui-patterns/src/MultiSelectDeprecated/Badges.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { X } from 'lucide-react' - -/** - * @deprecated Use ./multi-select instead - */ -export const BadgeDisabled = ({ name }: { name: string }) => ( -
    - {name} -
    -) - -/** - * @deprecated Use ./multi-select instead - */ -export const BadgeSelected = ({ - name, - handleRemove, -}: { - name: string - handleRemove: () => void -}) => ( -
    e.preventDefault()} - > - {name} - { - e.preventDefault() - e.stopPropagation() - handleRemove() - }} - /> -
    -) diff --git a/packages/ui-patterns/src/MultiSelectDeprecated/MultiSelectV2.tsx b/packages/ui-patterns/src/MultiSelectDeprecated/MultiSelectV2.tsx deleted file mode 100644 index 799cc9b8e9f..00000000000 --- a/packages/ui-patterns/src/MultiSelectDeprecated/MultiSelectV2.tsx +++ /dev/null @@ -1,159 +0,0 @@ -import { orderBy, without } from 'lodash' -import { Check, ChevronDown } from 'lucide-react' -import { ReactNode, useState } from 'react' -import { - cn, - Command_Shadcn_, - CommandEmpty_Shadcn_, - CommandGroup_Shadcn_, - CommandInput_Shadcn_, - CommandItem_Shadcn_, - CommandList_Shadcn_, - Popover_Shadcn_, - PopoverContent_Shadcn_, - PopoverTrigger_Shadcn_, - ScrollArea, -} from 'ui' - -import { BadgeDisabled, BadgeSelected } from './Badges' - -interface MultiSelectOption { - id: string | number - value: string - name: string - description?: string - disabled: boolean -} - -interface MultiSelectProps { - value: string[] - options: MultiSelectOption[] - placeholder?: string | ReactNode - searchPlaceholder?: string - disabled?: boolean - onChange?(x: string[]): void -} - -/** - * @deprecated Use ./multi-select instead - */ -export const MultiSelectV2 = ({ - options, - value, - placeholder, - searchPlaceholder = 'Search for option', - disabled = false, - onChange = () => {}, -}: MultiSelectProps) => { - const [open, setOpen] = useState(false) - const [selected, setSelected] = useState(value || []) - - // Selected is `value` if defined, otherwise use local useState - const selectedOptions = value || selected - - // Order the options so disabled items are at the beginning - const formattedOptions = orderBy(options, ['disabled'], ['desc']) - - const checkIfActive = (option: MultiSelectOption) => { - const isOptionSelected = (selectedOptions || []).find((x) => x === option.value) - return isOptionSelected !== undefined - } - - const handleChange = (option: MultiSelectOption) => { - const _selected = selectedOptions - const isActive = checkIfActive(option) - - const updatedPayload = isActive - ? [...without(_selected, option.value)] - : [..._selected.concat([option.value])] - - // Payload must always include disabled options - const compulsoryOptions = options - .filter((option) => option.disabled) - .map((option) => option.name) - - const formattedPayload = [...new Set(updatedPayload.concat(compulsoryOptions))] - - setSelected(formattedPayload) - onChange(formattedPayload) - } - - return ( -
    - - -
    setOpen(true)} - > - {selectedOptions.length === 0 && placeholder && ( -
    - {placeholder} -
    - )} - {selectedOptions.map((value, idx) => { - const id = `${value}-${idx}` - const option = formattedOptions.find((x) => x.value === value) - const isDisabled = option?.disabled ?? false - if (!option) { - return <> - } else if (isDisabled) { - return - } else { - return ( - handleChange(option)} - /> - ) - } - })} -
    - -
    -
    -
    - - - - event.stopPropagation()}> - No options found - - 7 ? 'h-[210px]' : '')}> - {formattedOptions?.map((option) => { - const active = - selectedOptions && - selectedOptions.find((selected) => { - return selected === option.value - }) - ? true - : false - return ( - handleChange(option)} - onSelect={() => handleChange(option)} - > -
    - {option.name} - {active && } -
    -
    - ) - })} -
    -
    -
    -
    -
    -
    -
    - ) -} diff --git a/packages/ui-patterns/src/MultiSelectDeprecated/index.tsx b/packages/ui-patterns/src/MultiSelectDeprecated/index.tsx deleted file mode 100644 index c4cca1e2d50..00000000000 --- a/packages/ui-patterns/src/MultiSelectDeprecated/index.tsx +++ /dev/null @@ -1,274 +0,0 @@ -import clsx from 'clsx' -import { filter, orderBy, without } from 'lodash' -import { AlertCircle, Check, ChevronDown, Plus, Search } from 'lucide-react' -import { ReactNode, useEffect, useRef, useState } from 'react' -import { - Input, - Popover_Shadcn_, - PopoverContent_Shadcn_, - PopoverTrigger_Shadcn_, - ScrollArea, -} from 'ui' - -import { BadgeDisabled, BadgeSelected } from './Badges' - -export interface MultiSelectOption { - id: string | number - value: string - name: string - description?: string - disabled: boolean -} - -interface Props { - value: string[] - options: MultiSelectOption[] - label?: string - error?: string - placeholder?: string | ReactNode - searchPlaceholder?: string - descriptionText?: string | ReactNode - emptyMessage?: string | ReactNode - disabled?: boolean - allowDuplicateSelection?: boolean - onChange?(x: string[]): void -} - -/** - * Copy styling from supabase/ui default.theme - * input base + standard - */ - -/** - * @deprecated Use ./multi-select instead - */ -export default function MultiSelect({ - options, - value, - label, - error, - descriptionText, - placeholder, - searchPlaceholder = 'Search for option', - emptyMessage, - disabled, - allowDuplicateSelection = false, - onChange = () => {}, -}: Props) { - const ref = useRef(null) - - const [open, setOpen] = useState(false) - const [selected, setSelected] = useState(value || []) - const [searchString, setSearchString] = useState('') - const [inputWidth, setInputWidth] = useState(128) - - // Selected is `value` if defined, otherwise use local useState - const selectedOptions = value || selected - - // Calculate width of the Popover - useEffect(() => { - setInputWidth(ref.current ? (ref.current as any).offsetWidth : inputWidth) - }, [ref.current]) - - useEffect(() => { - if (!open) setSearchString('') - }, [open]) - - const width = `${inputWidth}px` - - // Order the options so disabled items are at the beginning - const formattedOptions = orderBy(options, ['disabled'], ['desc']) - - // Options to show in Popover menu - const filteredOptions = - searchString.length > 0 - ? filter(formattedOptions, (option) => !option.disabled && option.name.includes(searchString)) - : filter(formattedOptions, { disabled: false }) - - const checkIfActive = (option: MultiSelectOption) => { - const isOptionSelected = (selectedOptions || []).find((x) => x === option.value) - return isOptionSelected !== undefined - } - - const handleRemove = (idx: number) => { - const updatedSelected = selected.filter((_x, index) => index !== idx) - setSelected(updatedSelected) - onChange(updatedSelected) - } - - const handleChange = (option: MultiSelectOption) => { - const _selected = selectedOptions - const isActive = checkIfActive(option) - - const updatedPayload = allowDuplicateSelection - ? [..._selected.concat([option.value])] - : isActive - ? [...without(_selected, option.value)] - : [..._selected.concat([option.value])] - - // Payload must always include disabled options - const compulsoryOptions = options - .filter((option) => option.disabled) - .map((option) => option.name) - - const formattedPayload = allowDuplicateSelection - ? updatedPayload.concat(compulsoryOptions) - : [...new Set(updatedPayload.concat(compulsoryOptions))] - - setSelected(formattedPayload) - onChange(formattedPayload) - } - - return ( -
    - {label && } -
    - - -
    setOpen(true)} - > - {selectedOptions.length === 0 && placeholder && ( -
    - {placeholder} -
    - )} - {selectedOptions.map((value, idx) => { - const id = `${value}-${idx}` - const option = formattedOptions.find((x) => x.value === value) - const isDisabled = option?.disabled ?? false - if (!option) { - return <> - } else if (isDisabled) { - return - } else { - return ( - - allowDuplicateSelection ? handleRemove(idx) : handleChange(option) - } - /> - ) - } - })} -
    - -
    -
    -
    - - } - placeholder={searchPlaceholder} - value={searchString} - onChange={(e) => setSearchString(e.target.value)} - /> - 5 ? 'h-[225px]' : '')}> - {filteredOptions.length >= 1 ? ( - filteredOptions.map((option) => { - const active = - !allowDuplicateSelection && - selectedOptions && - selectedOptions.find((selected) => { - return selected === option.value - }) - ? true - : false - - return ( -
    handleChange(option)} - className={[ - 'text-typography-body-light [[data-theme*=dark]_&]:text-typography-body-dark', - 'group flex cursor-pointer items-center justify-between transition', - 'space-x-1 rounded bg-transparent p-2 px-4 text-sm hover:bg-overlay-hover', - `${active ? ' [[data-theme*=dark]_&]:bg-green-600/25' : ''}`, - ].join(' ')} - > -
    -

    {option.name}

    - {option.description !== undefined && ( -

    {option.description}

    - )} -
    - {active && ( - - )} - {allowDuplicateSelection && ( -
    - -

    Add value

    -
    - )} -
    - ) - }) - ) : options.length === 0 ? ( -
    - {emptyMessage ? ( - emptyMessage - ) : ( -
    - -

    No options available

    -
    - )} -
    - ) : ( -
    - {emptyMessage ? ( - emptyMessage - ) : ( -
    -

    No options found

    -
    - )} -
    - )} -
    -
    -
    -
    - - {descriptionText && ( - {descriptionText} - )} - {error && {error}} -
    - ) -} diff --git a/packages/ui-patterns/src/multi-select/multi-select.tsx b/packages/ui-patterns/src/multi-select/multi-select.tsx index f1190b62d4a..1039675ca9d 100644 --- a/packages/ui-patterns/src/multi-select/multi-select.tsx +++ b/packages/ui-patterns/src/multi-select/multi-select.tsx @@ -2,8 +2,17 @@ import { cva, VariantProps } from 'class-variance-authority' import { Check, ChevronsUpDown, X as RemoveIcon } from 'lucide-react' +// @ts-ignore Required to avoid TS error: The inferred type of MultiSelectorContent cannot be named without a reference to @radix-ui +import type { Popover as PopoverPrimitive } from 'radix-ui' import React, { useEffect } from 'react' -import { Badge, cn, Popover_Shadcn_ as Popover, PopoverAnchor_Shadcn_ as PopoverAnchor } from 'ui' +import { + Badge, + cn, + Popover_Shadcn_ as Popover, + PopoverAnchor_Shadcn_ as PopoverAnchor, + PopoverContent_Shadcn_ as PopoverContent, + PopoverContentProps_Shadcn_ as PopoverContentProps, +} from 'ui' import { Command, CommandEmpty, @@ -11,7 +20,6 @@ import { CommandItem, CommandList, } from 'ui/src/components/shadcn/ui/command' -import { PopoverContent } from 'ui/src/components/shadcn/ui/popover' import { SIZE_VARIANTS, SIZE_VARIANTS_DEFAULT } from 'ui/src/lib/constants' interface MultiSelectContextProps { @@ -286,7 +294,7 @@ const MultiSelectorTrigger = React.forwardRef>( - ({ className, children }, ref) => { +const MultiSelectorContent = React.forwardRef( + ({ className, children, ...props }, ref) => { const { id } = useMultiSelect() return ( {children} diff --git a/packages/ui/index.tsx b/packages/ui/index.tsx index 1e2c20d7751..a5a207e7642 100644 --- a/packages/ui/index.tsx +++ b/packages/ui/index.tsx @@ -133,6 +133,7 @@ export { PopoverContent as PopoverContent_Shadcn_, PopoverAnchor as PopoverAnchor_Shadcn_, PopoverSeparator as PopoverSeparator_Shadcn_, + type PopoverContentProps as PopoverContentProps_Shadcn_, } from './src/components/shadcn/ui/popover' export { diff --git a/packages/ui/src/components/shadcn/ui/popover.tsx b/packages/ui/src/components/shadcn/ui/popover.tsx index 74702c74c48..2e93ef76857 100644 --- a/packages/ui/src/components/shadcn/ui/popover.tsx +++ b/packages/ui/src/components/shadcn/ui/popover.tsx @@ -10,7 +10,7 @@ const Popover = PopoverPrimitive.Root const PopoverTrigger = PopoverPrimitive.Trigger const PopoverAnchor = PopoverPrimitive.Anchor -type PopoverContentProps = { +export type PopoverContentProps = { align?: 'center' | 'start' | 'end' sideOffset?: number sameWidthAsTrigger?: boolean From c23275d4c6e1dfecb7ab8a14856a88646b505f80 Mon Sep 17 00:00:00 2001 From: "K-Dog (Kevin)" Date: Thu, 30 Apr 2026 17:07:15 +0800 Subject: [PATCH 12/21] chore: bump stripe deps (#44930) --- apps/studio/package.json | 4 ++-- pnpm-lock.yaml | 24 ++++++++++++------------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/apps/studio/package.json b/apps/studio/package.json index 2db944aeb16..31144c6bd89 100644 --- a/apps/studio/package.json +++ b/apps/studio/package.json @@ -55,8 +55,8 @@ "@number-flow/react": "^0.3.2", "@sentry/nextjs": "catalog:", "@std/path": "npm:@jsr/std__path@^1.0.8", - "@stripe/react-stripe-js": "^3.7.0", - "@stripe/stripe-js": "^7.5.0", + "@stripe/react-stripe-js": "6.1.0", + "@stripe/stripe-js": "9.1.0", "@supabase/auth-js": "catalog:", "@supabase/mcp-server-supabase": "^0.7.0", "@supabase/mcp-utils": "^0.4.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1b0b3f0a58c..9d3769db96f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -929,11 +929,11 @@ importers: specifier: npm:@jsr/std__path@^1.0.8 version: '@jsr/std__path@1.0.8' '@stripe/react-stripe-js': - specifier: ^3.7.0 - version: 3.7.0(@stripe/stripe-js@7.5.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: 6.1.0 + version: 6.1.0(@stripe/stripe-js@9.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@stripe/stripe-js': - specifier: ^7.5.0 - version: 7.5.0 + specifier: 9.1.0 + version: 9.1.0 '@supabase/auth-js': specifier: 'catalog:' version: 2.104.1 @@ -7663,15 +7663,15 @@ packages: '@stitches/core@1.2.8': resolution: {integrity: sha512-Gfkvwk9o9kE9r9XNBmJRfV8zONvXThnm1tcuojL04Uy5uRyqg93DC83lDebl0rocZCfKSjUv+fWYtMQmEDJldg==} - '@stripe/react-stripe-js@3.7.0': - resolution: {integrity: sha512-PYls/2S9l0FF+2n0wHaEJsEU8x7CmBagiH7zYOsxbBlLIHEsqUIQ4MlIAbV9Zg6xwT8jlYdlRIyBTHmO3yM7kQ==} + '@stripe/react-stripe-js@6.1.0': + resolution: {integrity: sha512-LbKbRv4+wUSHLb5VNxqiYcKaqXPvTju0bJaF0RrzH0h4+aKWDXAk4RzUBcpNxxj8KtjuxICElANs1Li7aTv1IQ==} peerDependencies: - '@stripe/stripe-js': '>=1.44.1 <8.0.0' + '@stripe/stripe-js': '>=9.0.0 <10.0.0' react: '>=16.8.0 <20.0.0' react-dom: '>=16.8.0 <20.0.0' - '@stripe/stripe-js@7.5.0': - resolution: {integrity: sha512-Cq3KKe+G1o7PSBMbmrgpT2JgBeyH2THHr3RdIX2MqF7AnBuspIMgtZ3ktcCgP7kZsTMvnmWymr7zZCT1zeWbMw==} + '@stripe/stripe-js@9.1.0': + resolution: {integrity: sha512-v51LoEfZNiNS/5DcarWPCYgn24w4dqwwALR4GTbMW/N0DDzzj4DgYNoixX6PYvpt6uIJMucGUabn/BHhylggIQ==} engines: {node: '>=12.16'} '@supabase-labs/y-supabase@0.1.0': @@ -24156,14 +24156,14 @@ snapshots: '@stitches/core@1.2.8': {} - '@stripe/react-stripe-js@3.7.0(@stripe/stripe-js@7.5.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@stripe/react-stripe-js@6.1.0(@stripe/stripe-js@9.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@stripe/stripe-js': 7.5.0 + '@stripe/stripe-js': 9.1.0 prop-types: 15.8.1 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - '@stripe/stripe-js@7.5.0': {} + '@stripe/stripe-js@9.1.0': {} '@supabase-labs/y-supabase@0.1.0': dependencies: From 3b4cd016509847c145c6e466354a7a5804e5bba1 Mon Sep 17 00:00:00 2001 From: Mert YEREKAPAN <33198490+myerekapan@users.noreply.github.com> Date: Thu, 30 Apr 2026 11:26:54 +0200 Subject: [PATCH 13/21] feat(pricing): show multi-project pricing copy on Pro/Team plan cards (#45375) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Following up on #45361 — instead of keeping the control, we're baking in the `multi_project` variant from the original A/B experiment (#44293) as the new default for Pro/Team plan cards. **Change:** On Pro and Team plan cards, the warning area now reads: > First project included. Additional projects from \$10/mo. > See how pricing scales → Replacing the previous control copy (`plan.warning` + "Need more compute?" link). Free and Enterprise plans still show their original `plan.warning` text. No A/B test infrastructure restored — the variant is the new default. ## Summary by CodeRabbit * **New Features** * Updated pricing card messaging to display consistent information about included projects and additional project pricing for premium plans * Enhanced visibility of compute add-on scaling details with improved link labeling * Adjusted warning message display logic for standard plans --- apps/www/components/Pricing/PricingPlans.tsx | 28 ++++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/apps/www/components/Pricing/PricingPlans.tsx b/apps/www/components/Pricing/PricingPlans.tsx index 6d9fd86fbab..591dca2c470 100644 --- a/apps/www/components/Pricing/PricingPlans.tsx +++ b/apps/www/components/Pricing/PricingPlans.tsx @@ -120,20 +120,26 @@ const PricingPlans = ({ organizations, hasExistingOrganizations }: PricingPlansP

- {plan.warning && ( + {isUpgradablePlan ? (
- - {plan.warning} + + First project included. Additional projects from $10/mo. - {(plan.name === 'Pro' || plan.name === 'Team') && ( - - Need more compute? - - )} + + See how pricing scales +
+ ) : ( + plan.warning && ( +
+ + {plan.warning} + +
+ ) )}
From f6c4b5639ff202052db7f5e3e7cd0ffc5d7a21eb Mon Sep 17 00:00:00 2001 From: Pedro Rodrigues <44656907+Rodriguespn@users.noreply.github.com> Date: Thu, 30 Apr 2026 11:47:49 +0200 Subject: [PATCH 14/21] fix(studio): update Antigravity OAuth troubleshooting instructions (#45398) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - Replaces the outdated command palette (`Authentication: Remove Dynamic Authentication Providers`) troubleshooting step with the new Antigravity flow: Agent Settings (Cmd+,/Ctrl+,) → Customizations tab → Authenticate button next to the server. image ## Summary by CodeRabbit * **Documentation** * Updated Antigravity UI help text for authentication with clearer step-by-step guidance to open Agent Settings and use the Authenticate button for Supabase; included a screenshot showing where the button appears. * **Chores** * Added support for PNG asset imports so image files used in the UI are recognized and handled correctly. --------- Co-authored-by: Claude Sonnet 4.6 --- .../antigravity-authenticate-screenshot.png | Bin 0 -> 42689 bytes .../ui-patterns/src/McpUrlBuilder/constants.tsx | 16 +++++++++++++--- packages/ui-patterns/src/types/assets.d.ts | 5 +++++ 3 files changed, 18 insertions(+), 3 deletions(-) create mode 100644 packages/ui-patterns/src/McpUrlBuilder/assets/antigravity-authenticate-screenshot.png diff --git a/packages/ui-patterns/src/McpUrlBuilder/assets/antigravity-authenticate-screenshot.png b/packages/ui-patterns/src/McpUrlBuilder/assets/antigravity-authenticate-screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..e661c770b5dceb5e8f0117a7023596fec2002018 GIT binary patch literal 42689 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

- If you run into authentication issues, open the command palette and run{' '} - Authentication: Remove Dynamic Authentication Providers to clear cached - OAuth credentials and re-authenticate. + If you run into authentication issues, open Agent Settings with Cmd+,{' '} + (Mac) or Ctrl+, (Windows/Linux), navigate to the{' '} + Customizations tab, and click the Authenticate button + next to the Supabase server.

+ Antigravity MCP server settings showing the Authenticate button next to the Supabase server
), }, diff --git a/packages/ui-patterns/src/types/assets.d.ts b/packages/ui-patterns/src/types/assets.d.ts index adb1bc31dbb..31d6fe63736 100644 --- a/packages/ui-patterns/src/types/assets.d.ts +++ b/packages/ui-patterns/src/types/assets.d.ts @@ -7,3 +7,8 @@ declare module '*.svg' { const content: any export default content } + +declare module '*.png' { + const content: string + export default content +} From dfa3a860c329d664d9040858cf82ad6944a38ce4 Mon Sep 17 00:00:00 2001 From: Wen Bo Xie Date: Thu, 30 Apr 2026 18:05:07 +0800 Subject: [PATCH 15/21] fix: update edge functions jwks endpoint in docs (#45402) --- apps/docs/content/guides/functions/secrets.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/docs/content/guides/functions/secrets.mdx b/apps/docs/content/guides/functions/secrets.mdx index 00d09e0db91..fc6afbae61d 100644 --- a/apps/docs/content/guides/functions/secrets.mdx +++ b/apps/docs/content/guides/functions/secrets.mdx @@ -13,7 +13,7 @@ Edge Functions have access to these secrets by default: - `SUPABASE_DB_URL`: The URL for your Postgres database. You can use this to connect directly to your database - `SUPABASE_PUBLISHABLE_KEYS`: The `publishable` keys JSON dictionary for your Supabase API. This is safe to use in a browser when you have Row Level Security enabled - `SUPABASE_SECRET_KEYS`: The `secret` keys JSON dictionary for your Supabase API. This is safe to use in Edge Functions, but it should NEVER be used in a browser. This key will bypass Row Level Security -- `SUPABASE_JWKS`: The JSON Web Key Set used to verify user JWTs. Same value served at `https://.supabase.co/auth/v1/jwks` +- `SUPABASE_JWKS`: The JSON Web Key Set used to verify user JWTs. Same value served at `https://.supabase.co/auth/v1/.well-known/jwks.json` Legacy keys: From 56de26fe22e5923fe5376b1f45cae87cbd162411 Mon Sep 17 00:00:00 2001 From: Ivan Vasilov Date: Thu, 30 Apr 2026 13:53:24 +0300 Subject: [PATCH 16/21] chore: Migrate the monorepo to use Tailwind v4 (#45318) This PR migrates the whole monorepo to use Tailwind v4: - Removed `@tailwindcss/container-queries` plugin since it's included by default in v4, - Bump all instances of Tailwind to v4. Made minimal changes to the shared config to remove non-supported features (`alpha` mentions), - Migrate all apps to be compatible with v4 configs, - Fix the `typography.css` import in 3 apps, - Add missing rules which were included by default in v3, - Run `pnpm dlx @tailwindcss/upgrade` on all apps, which renames a lot of classes - Rename all misnamed classes according to https://tailwindcss.com/docs/upgrade-guide#renamed-utilities in all apps. --------- Co-authored-by: Jordi Enric --- .../default/block/chart-composed-metrics.tsx | 4 +- apps/design-system/app/layout.tsx | 2 +- apps/design-system/components/class-label.tsx | 2 +- .../components/code-block-wrapper.tsx | 2 +- .../components/code-fragment.tsx | 6 +- apps/design-system/components/colors.tsx | 2 +- .../design-system/components/command-menu.tsx | 4 +- .../components/component-preview.tsx | 14 +- apps/design-system/components/grid.tsx | 4 +- apps/design-system/components/icons.tsx | 7 +- .../components/mdx-components.tsx | 13 +- .../components/theme-switcher-dropdown.tsx | 2 +- .../components/top-navigation.tsx | 2 +- .../content/docs/components/chart.mdx | 2 +- .../content/docs/components/field.mdx | 22 +- .../content/docs/components/form.mdx | 2 +- .../content/docs/components/sidebar.mdx | 6 +- .../content/docs/components/typography.mdx | 2 +- apps/design-system/package.json | 3 +- .../default/block/chart-composed-metrics.tsx | 4 +- .../example/assistant-chat-commands.tsx | 2 +- .../default/example/chart-tooltip-demo.tsx | 18 +- .../color-usage-surface-studio-frame.tsx | 10 +- .../default/example/command-dialog.tsx | 2 +- .../default/example/data-grid-demo.tsx | 5 +- .../default/example/data-grid-empty-state.tsx | 5 +- .../registry/default/example/field-hear.tsx | 6 +- .../example/form-patterns-pagelayout.tsx | 2 +- .../example/form-patterns-sidepanel.tsx | 4 +- .../default/example/inner-side-menu-empty.tsx | 2 +- .../default/example/navigation-menu-demo.tsx | 4 +- .../example/navigation-menu-responsive.tsx | 4 +- .../example/scroll-area-horizontal-demo.tsx | 2 +- .../registry/default/example/toc-demo.tsx | 2 +- .../default/example/toc-single-demo.tsx | 2 +- .../default/example/typography-demo.tsx | 34 +- .../example/typography-inline-code.tsx | 2 +- .../registry/default/example/typography-p.tsx | 2 +- .../default/example/typography-table.tsx | 16 +- apps/design-system/styles/globals.css | 12 +- apps/design-system/styles/mdx.css | 2 + apps/design-system/tailwind.config.js | 2 +- apps/docs/app/graphiql/page.tsx | 2 +- .../database/database-advisors/page.tsx | 2 +- .../deployment/terraform/reference/page.tsx | 4 +- apps/docs/app/guides/troubleshooting/page.tsx | 2 +- .../AuthSmsProviderConfig.tsx | 6 +- .../components/BodyContentTypeDropdown.tsx | 2 +- apps/docs/components/ButtonCard.tsx | 2 +- apps/docs/components/Feedback/Feedback.tsx | 12 +- apps/docs/components/HomePageCover.tsx | 2 +- .../NavigationMenu/GlobalMobileMenu.tsx | 16 +- .../NavigationMenu/GlobalNavigationMenu.tsx | 10 +- .../NavigationMenu/NavigationMenuCliList.tsx | 2 +- .../NavigationMenuRefListItems.tsx | 2 +- .../Navigation/NavigationMenu/TopNavBar.tsx | 12 +- .../NavigationMenu/TopNavDropdown.tsx | 2 +- apps/docs/components/Navigation/SideBar.tsx | 10 +- apps/docs/components/RefVersionDropdown.tsx | 2 +- apps/docs/components/RegionsList.tsx | 4 +- apps/docs/components/StepHike/index.tsx | 2 +- apps/docs/components/Table.tsx | 2 +- .../docs/content/guides/auth/signing-keys.mdx | 24 +- .../docs/content/guides/database/orioledb.mdx | 4 +- .../content/guides/database/partitions.mdx | 2 +- .../branching/github-integration.mdx | 2 +- .../guides/getting-started/api-keys.mdx | 8 +- .../guides/platform/project-transfer.mdx | 4 +- .../content/guides/platform/read-replicas.mdx | 2 +- .../docs/content/guides/queues/quickstart.mdx | 6 +- .../docs/content/guides/telemetry/metrics.mdx | 2 +- .../telemetry/metrics/grafana-cloud.mdx | 2 +- .../telemetry/metrics/grafana-self-hosted.mdx | 2 +- .../telemetry/metrics/vendor-agnostic.mdx | 2 +- .../self-hosting-functions/introduction.mdx | 2 +- .../features/directives/CodeSample.client.tsx | 2 +- .../directives/CodeTabs.components.tsx | 2 +- .../docs/features/docs/Reference.sections.tsx | 16 +- apps/docs/features/docs/Reference.ui.tsx | 33 +- .../features/docs/Troubleshooting.page.tsx | 4 +- .../docs/Troubleshooting.ui.client.tsx | 7 +- .../docs/features/docs/Troubleshooting.ui.tsx | 2 +- .../ui/CodeBlock/CodeBlock.client.tsx | 2 +- apps/docs/features/ui/InfoTooltip.tsx | 6 +- apps/docs/layouts/MainSkeleton.tsx | 14 +- apps/docs/layouts/ref/RefSubLayout.tsx | 2 +- apps/docs/package.json | 1 - apps/docs/styles/main.css | 18 +- apps/docs/styles/prism-okaidia.css | 1 + apps/docs/tailwind.config.cjs | 1 - apps/learn/components/command.tsx | 2 +- apps/learn/components/course-hero.tsx | 4 +- apps/learn/components/mdx-components.tsx | 9 +- .../components/theme-switcher-dropdown.tsx | 2 +- apps/learn/components/what-will-i-learn.tsx | 8 +- apps/learn/package.json | 5 +- apps/learn/styles/globals.css | 6 +- apps/learn/styles/mdx.css | 2 + apps/lite-studio/app/app.css | 6 +- apps/lite-studio/app/root.tsx | 1 - apps/lite-studio/package.json | 1 - .../grid/components/editor/DateTimeEditor.tsx | 8 +- .../grid/components/editor/JsonEditor.tsx | 6 +- .../grid/components/editor/TextEditor.tsx | 4 +- .../formatter/ReferenceRecordPeek.tsx | 2 +- .../grid/components/grid/ColumnHeader.tsx | 12 +- .../components/grid/ColumnOverlayItem.tsx | 2 +- .../components/grid/components/grid/Grid.tsx | 2 +- .../grid/components/grid/GridError.tsx | 8 +- .../grid/components/header/Header.tsx | 8 +- .../grid/components/menu/RowContextMenu.tsx | 2 +- .../grid/context/TableIndexAdvisorContext.tsx | 2 +- .../interfaces/APIKeys/APIKeyRow.tsx | 2 +- .../APIKeys/ApiKeysIllustrations.tsx | 12 +- .../APIKeys/CreateNewAPIKeysButton.tsx | 2 +- .../interfaces/APIKeys/SecretAPIKeys.tsx | 2 +- .../TokenPermissionSection.tsx | 2 +- .../AccessTokenTable/TableContainer.tsx | 2 +- .../Scoped/NewScopedTokenSheet.tsx | 4 +- .../AccessTokens/Scoped/ViewTokenSheet.tsx | 2 +- .../interfaces/Account/AuditLogs.tsx | 10 +- .../Preferences/DeleteAccountButton.tsx | 2 +- .../Account/TOTPFactors/AddNewFactorModal.tsx | 2 +- .../interfaces/Advisors/AdvisorRuleItem.tsx | 4 +- .../interfaces/Advisors/AdvisorRules.tsx | 2 +- .../Advisors/AdvisorRules.utils.tsx | 2 +- .../ContextSearchResults.shared.tsx | 8 +- .../App/CommandMenu/CreateCommands.tsx | 2 +- .../FeaturePreview/AdvisorRulesPreview.tsx | 4 +- .../App/FeaturePreview/CLSPreview.tsx | 4 +- .../FeaturePreview/FeaturePreviewModal.tsx | 8 +- .../FeaturePreview/InlineEditorPreview.tsx | 2 +- .../App/FeaturePreview/PgDeltaDiffPreview.tsx | 2 +- .../App/FeaturePreview/RLSTesterPreview.tsx | 4 +- .../App/FeaturePreview/UnifiedLogsPreview.tsx | 4 +- .../interfaces/Auth/AuditLogsForm.tsx | 2 +- .../AuthProvidersForm.constants.ts | 2 +- .../CreateOrUpdateCustomProviderSheet.tsx | 2 +- .../CustomAuthProvidersList.tsx | 4 +- .../Auth/EmailTemplates/SpamValidation.tsx | 2 +- .../Auth/EmailTemplates/TemplateEditor.tsx | 6 +- .../interfaces/Auth/Hooks/HookCard.tsx | 2 +- .../OAuthApps/CreateOrUpdateOAuthAppSheet.tsx | 2 +- .../Auth/OAuthApps/OAuthAppsList.tsx | 4 +- .../Auth/Overview/OverviewMetrics.tsx | 6 +- .../PolicyEditorPanelHeader.tsx | 6 +- .../PolicyEditorPanel/PolicyTemplates.tsx | 6 +- .../PolicyEditorPanel/RLSCodeEditor.tsx | 4 +- .../Auth/Policies/PolicyEditorPanel/index.tsx | 6 +- .../Auth/Policies/PolicySelection.tsx | 4 +- .../Policies/PolicyTableRow/PolicyRow.tsx | 2 +- .../Auth/RLSTester/RLSTableCard.tsx | 6 +- .../Auth/RLSTester/RLSTesterResults.tsx | 24 +- .../Auth/RLSTester/RLSTesterSheet.tsx | 4 +- .../Auth/RLSTester/UserSelector.tsx | 2 +- .../Auth/RedirectUrls/AddNewURLModal.tsx | 2 +- .../Auth/RedirectUrls/ValueContainer.tsx | 2 +- .../ThirdPartyAuthForm/IntegrationCard.tsx | 2 +- .../Auth/ThirdPartyAuthForm/index.tsx | 2 +- .../interfaces/Auth/Users/AddUserDropdown.tsx | 2 +- .../interfaces/Auth/Users/UserLogs.tsx | 2 +- .../interfaces/Auth/Users/UserOverview.tsx | 6 +- .../interfaces/Auth/Users/UserPanel.tsx | 6 +- .../interfaces/Auth/Users/Users.utils.tsx | 2 +- .../Auth/Users/UsersGridComponents.tsx | 2 +- .../interfaces/Auth/Users/UsersSearch.tsx | 4 +- .../interfaces/Auth/Users/UsersV2.tsx | 6 +- .../Billing/NoProjectsOnPaidOrgInfo.tsx | 2 +- .../Payment/AddNewPaymentMethodModal.tsx | 6 +- .../Billing/Payment/AddPaymentMethodForm.tsx | 10 +- .../Payment/PaymentMethods/CreditCard.tsx | 2 +- .../BranchManagement/BranchPanels.tsx | 4 +- .../BranchManagement/DatabaseDiffPanel.tsx | 2 +- .../BranchManagement/WorkflowLogs.tsx | 2 +- .../BranchManagement/WorkflowLogsCard.tsx | 6 +- .../interfaces/Connect/ConnectionPanel.tsx | 16 +- .../Connect/ConnectionParameters.tsx | 6 +- .../Connect/DatabaseConnectionString.tsx | 8 +- .../ConnectSheet/ConnectConfigSection.tsx | 2 +- .../ConnectSheet/ConnectSheetStep.tsx | 2 +- .../ConnectSheet/ConnectionParameters.tsx | 2 +- .../ConnectSheet/CopyPromptAdmonition.tsx | 2 +- .../ConnectSheet/FrameworkSelector.tsx | 2 +- .../direct-connection/IPv4StatusPanel.tsx | 14 +- .../mcp/claude-code/authenticate/content.tsx | 2 +- .../steps/mcp/codex/verify/content.tsx | 4 +- .../Database/Backups/BackupItem.tsx | 2 +- .../Database/Backups/BackupsList.tsx | 2 +- .../Database/Backups/PITR/PITRForm.tsx | 6 +- .../Database/Backups/PITR/PITRNotice.tsx | 2 +- .../Database/Backups/PITR/TimeInput.tsx | 6 +- .../RestoreToNewProject/BackupsList.tsx | 2 +- .../CreateEnumeratedTypeSidePanel.tsx | 2 +- .../EditEnumeratedTypeSidePanel.tsx | 2 +- .../EnumeratedTypeValueRow.tsx | 2 +- .../Extensions/ExtensionCardSkeleton.tsx | 2 +- .../Database/Extensions/Extensions.tsx | 4 +- .../CreateFunction/CreateFunctionHeader.tsx | 2 +- .../CreateFunction/FunctionEditor.tsx | 2 +- .../Functions/CreateFunction/index.tsx | 6 +- .../Database/Hooks/FormContents.tsx | 12 +- .../interfaces/Database/Hooks/HTTPHeaders.tsx | 4 +- .../Database/Hooks/HTTPParameters.tsx | 4 +- .../Database/Hooks/HTTPRequestConfig.tsx | 6 +- .../Database/Hooks/HooksList/SchemaTable.tsx | 2 +- .../Database/Indexes/CreateIndexSidePanel.tsx | 10 +- .../interfaces/Database/Indexes/Indexes.tsx | 2 +- .../Database/Migrations/Migrations.tsx | 2 +- .../Database/ProtectedSchemaWarning.tsx | 2 +- .../DestinationForm/AdvancedSettings.tsx | 2 +- .../DestinationPanelFields.tsx | 2 +- .../DestinationForm/PublicationSelection.tsx | 2 +- .../ValidationFailuresSection.tsx | 2 +- .../DestinationForm/index.tsx | 2 +- .../DestinationPanel/DestinationPanel.tsx | 4 +- .../DestinationTypeSelection.tsx | 2 +- .../ReadReplicaForm/index.tsx | 4 +- .../Replication/EnableReplicationCallout.tsx | 6 +- .../Replication/ErrorDetailsDialog.tsx | 4 +- .../ReadReplicas/ReadReplicaDetails.tsx | 2 +- .../Replication/ReplicationDiagram/Edges.tsx | 4 +- .../Replication/ReplicationDiagram/Nodes.tsx | 4 +- .../Replication/ReplicationDiagram/index.tsx | 4 +- .../ReplicationPipelineStatus.tsx | 10 +- .../SlotLagMetrics.tsx | 2 +- .../Replication/UpdateVersionModal.tsx | 4 +- .../PreviousRestoreItem.tsx | 4 +- .../interfaces/Database/Roles/RoleRow.tsx | 6 +- .../Database/Roles/RoleRowSkeleton.tsx | 4 +- .../interfaces/Database/Roles/RolesList.tsx | 2 +- .../Database/Schemas/DefaultEdge.tsx | 2 +- .../Database/Schemas/SchemaGraph.tsx | 8 +- .../Database/Schemas/SchemaTableNode.tsx | 11 +- .../interfaces/Database/Tables/ColumnList.tsx | 4 +- .../interfaces/Database/Tables/TableList.tsx | 4 +- .../Database/Triggers/ChooseFunctionForm.tsx | 6 +- .../Database/Triggers/TriggerSheet.tsx | 8 +- .../DiskManagement/DiskManagementForm.tsx | 6 +- .../DiskManagementReviewAndSubmitDialog.tsx | 6 +- .../fields/ComputeSizeField.tsx | 4 +- .../fields/StorageTypeField.tsx | 2 +- .../DiskManagement/ui/BillingChangeBadge.tsx | 2 +- .../DiskManagement/ui/DiskCountdownRadial.tsx | 2 +- .../ui/DiskManagementReadReplicas.tsx | 4 +- .../DiskManagement/ui/DiskSpaceBar.tsx | 12 +- .../Docs/PublicSchemaNotEnabledAlert.tsx | 6 +- .../ExplainVisualizer.NodeRow.tsx | 2 +- .../ExplainVisualizer.utils.ts | 2 +- .../EdgeFunctionDetails.tsx | 8 +- .../EdgeFunctionTesterSheet.tsx | 8 +- .../EdgeFunctionInvocationsChart.tsx | 4 +- .../EdgeFunctionInvocationsSection.tsx | 2 +- .../EdgeFunctionRecentErrors.tsx | 2 +- .../EdgeFunctionRecentInvocations.tsx | 2 +- .../EdgeFunctionSecret.tsx | 6 +- .../EdgeFunctionSecrets/EditSecretSheet.tsx | 4 +- .../Functions/EdgeFunctionsListItem.tsx | 2 +- .../Functions/FunctionsEmptyState.tsx | 6 +- .../interfaces/Functions/FunctionsNav.tsx | 2 +- .../Functions/TerminalInstructions.tsx | 4 +- .../interfaces/GraphQL/GraphiQL.tsx | 2 +- .../interfaces/GraphQL/graphiql.module.css | 2 + .../interfaces/Home/AdvisorWidget.tsx | 18 +- .../Home/NewProjectPanel/APIKeys.tsx | 2 +- .../Home/NewProjectPanel/GetStartedHero.tsx | 4 +- .../Home/NewProjectPanel/NewProjectPanel.tsx | 16 +- .../Home/ProjectList/ProjectCard.tsx | 2 +- .../Home/ProjectList/ProjectCardStatus.tsx | 2 +- .../Home/ProjectList/ProjectTableRow.tsx | 2 +- .../Home/ProjectList/ShimmeringCard.tsx | 2 +- .../interfaces/Home/ProjectUsage.tsx | 8 +- .../CronJobScheduleSection.tsx | 4 +- .../Integrations/CronJobs/CronJobs.utils.tsx | 2 +- .../CronJobs/CronJobsTab.CleanupNotice.tsx | 4 +- .../CronJobs/CronJobsTab.DataGrid.tsx | 2 +- .../CronJobsTab.EstimateErrorNotice.tsx | 2 +- .../CronJobs/EdgeFunctionSection.tsx | 2 +- .../CronJobs/HttpBodyFieldSection.tsx | 2 +- .../Integrations/CronJobs/PreviousRunsTab.tsx | 4 +- .../CronJobs/SqlSnippetSection.tsx | 2 +- .../AdvancedSettings.tsx | 2 +- .../InstallIntegrationSheet.tsx | 2 +- .../Integration/MarkdownContent.tsx | 4 +- .../Landing/AvailableIntegrations.tsx | 4 +- .../Landing/InstalledIntegrations.tsx | 2 +- .../Queues/CreateQueueSheet/RlsSection.tsx | 2 +- .../Integrations/Queues/QueueTab.tsx | 2 +- .../Integrations/Queues/Queues.utils.tsx | 10 +- .../Integrations/Queues/QueuesSettings.tsx | 2 +- .../Integrations/Queues/QueuesTab.tsx | 2 +- .../SingleQueue/MessageDetailsPanel.tsx | 2 +- .../Queues/SingleQueue/QueueDataGrid.tsx | 4 +- .../Queues/SingleQueue/SendMessageModal.tsx | 2 +- .../Vault/Secrets/Secrets.utils.tsx | 2 +- .../Vault/Secrets/SecretsManagement.tsx | 2 +- .../Vercel/OrganizationPicker.tsx | 2 +- .../VercelGithub/IntegrationPanels.tsx | 12 +- .../VercelGithub/ProjectLinker.tsx | 14 +- .../Integrations/Wrappers/ColumnType.tsx | 4 +- .../Integrations/Wrappers/OverviewTab.tsx | 2 +- .../Integrations/Wrappers/WrapperRow.tsx | 10 +- .../Wrappers/WrapperTableEditor.tsx | 2 +- .../StripeSyncSettingsPage.tsx | 2 +- .../JwtSecrets/algorithm-hover-card.tsx | 2 +- .../jwt-secret-keys-table/index.tsx | 2 +- .../interfaces/JwtSecrets/jwt-settings.tsx | 10 +- .../interfaces/JwtSecrets/jwt.constants.ts | 6 +- .../JwtSecrets/start-using-keys-banner.tsx | 2 +- .../interfaces/Linter/LintPageTabs.tsx | 12 +- .../interfaces/Linter/LinterDataGrid.tsx | 4 +- .../interfaces/Linter/LinterPageFooter.tsx | 2 +- .../interfaces/LogDrains/AnimatedLogos.tsx | 2 +- .../LogDrainDestinationSheetForm.tsx | 4 +- .../interfaces/LogDrains/LogDrains.tsx | 2 +- .../DatabaseInfrastructureSection.tsx | 10 +- .../Organization/AuditLogs/AuditLogs.tsx | 10 +- .../BillingBreakdown/BillingBreakdown.tsx | 2 +- .../BillingBreakdown/UpcomingInvoice.tsx | 16 +- .../BillingSettings/BillingEmail.tsx | 2 +- .../BillingSettings/BillingSettings.tsx | 2 +- .../CostControl/CostControl.tsx | 2 +- .../CostControl/SpendCapSidePanel.tsx | 2 +- .../BillingSettings/CreditCodeRedemption.tsx | 6 +- .../BillingSettings/CreditTopUp.tsx | 6 +- .../Subscription/EnterpriseCard.tsx | 4 +- .../Subscription/ExitSurveyModal.tsx | 4 +- .../Subscription/PaymentMethodSelection.tsx | 6 +- .../Subscription/PlanUpdateSidePanel.tsx | 4 +- .../Subscription/Subscription.tsx | 2 +- .../SubscriptionPlanUpdateDialog.tsx | 8 +- .../Subscription/UpgradeModal.tsx | 2 +- .../AwsMarketplaceAutoRenewalWarning.tsx | 2 +- .../AwsMarketplaceLinkExistingOrg.tsx | 4 +- .../interfaces/Organization/HeaderBanner.tsx | 2 +- .../InvoicesSettings/InvoicesSettings.tsx | 2 +- .../Organization/OAuthApps/OAuthAppRow.tsx | 4 +- .../Organization/OAuthApps/OAuthApps.tsx | 2 +- .../OAuthApps/PublishAppSidePanel/index.tsx | 6 +- .../Apps/AppsList/AppsListLoading.tsx | 4 +- .../Apps/AppsList/AppsListTable.tsx | 2 +- .../Apps/CreateAppSheet/CreateAppSheet.tsx | 6 +- .../Apps/ViewAppSheet/ViewAppSheet.tsx | 2 +- .../Apps/ViewAppSheet/ViewAppSheetInfo.tsx | 2 +- .../Organization/ProjectClaim/confirm.tsx | 6 +- .../TeamSettings/InviteMemberButton.tsx | 2 +- .../Organization/TeamSettings/MembersView.tsx | 4 +- .../TeamSettings/TeamSettings.tsx | 2 +- .../UpdateRolesPanel/UpdateRolesPanel.tsx | 7 +- .../Organization/Usage/TotalUsage.tsx | 2 +- .../interfaces/Organization/Usage/Usage.tsx | 4 +- .../Usage/UsageSection/DatabaseSizeUsage.tsx | 2 +- .../Usage/UsageSection/DiskUsage.tsx | 2 +- .../OrganizationInvite/OrganizationInvite.tsx | 2 +- .../PlatformWebhooksEndpointSheet.tsx | 2 +- .../ProjectAPIDocs/Content/Entity.tsx | 2 +- .../ProjectAPIDocs/ContentSnippet.tsx | 2 +- .../ProjectAPIDocs/FirstLevelNav.tsx | 10 +- .../ProjectAPIDocs/ResourceContent.tsx | 2 +- .../ProjectAPIDocs/SecondLevelNav.Layout.tsx | 6 +- .../ProjectCreation/AdvancedConfiguration.tsx | 12 +- .../ProjectCreation/OrganizationSelector.tsx | 2 +- .../ProjectCreation/RegionSelector.tsx | 8 +- .../interfaces/ProjectHome/ConnectSection.tsx | 4 +- .../ProjectHome/CustomReportSection.tsx | 4 +- .../interfaces/ProjectHome/Home.tsx | 2 +- .../ProjectHome/SortableSection.tsx | 2 +- .../QueryInsightsChart/QueryInsightsChart.tsx | 6 +- .../QueryInsightsChartTooltip.tsx | 2 +- .../QueryInsightsHealthMetric.tsx | 2 +- .../QueryInsightsHealthScoreSkeleton.tsx | 4 +- .../QueryInsightsDetailSheet.tsx | 8 +- .../QueryInsightsTable/QueryInsightsTable.tsx | 14 +- .../hooks/useQueryInsightsTableColumns.tsx | 16 +- .../IndexAdvisor/IndexAdvisorNotice.tsx | 2 +- .../IndexAdvisor/IndexSuggestionIcon.tsx | 2 +- .../QueryPerformance/QueryDetail.tsx | 4 +- .../QueryPerformance/QueryIndexes.tsx | 12 +- .../QueryPerformanceChart.tsx | 4 +- .../QueryPerformance/QueryPerformanceGrid.tsx | 16 +- .../QueryPerformanceMetrics.tsx | 4 +- .../QueryPerformance/SqlMonacoBlock.tsx | 2 +- .../WithStatements/WithStatements.tsx | 2 +- .../interfaces/Realtime/Inspector/Header.tsx | 2 +- .../Realtime/Inspector/MessageSelection.tsx | 4 +- .../Realtime/Inspector/MessagesTable.tsx | 14 +- .../RealtimeFilterPopover/TableSelector.tsx | 2 +- .../Inspector/RealtimeFilterPopover/index.tsx | 6 +- .../RealtimeMessageColumnRenderer.tsx | 4 +- .../SelectedRealtimeMessagePanel.tsx | 2 +- .../Realtime/Inspector/SendMessageModal.tsx | 2 +- .../ReportBlock/ReportBlockContainer.tsx | 2 +- .../interfaces/Reports/ReportFilterBar.tsx | 2 +- .../Reports/ReportFilterPopover.tsx | 2 +- .../components/interfaces/Reports/Reports.tsx | 2 +- .../Reports/renderers/ApiRenderers.tsx | 25 +- .../Reports/renderers/StorageRenderers.tsx | 4 +- .../Reports/v2/ReportsSelectFilter.tsx | 2 +- .../UserImpersonationSelector.tsx | 4 +- .../interfaces/SQLEditor/AskAIWidget.tsx | 2 +- .../SQLEditor/DownloadSnippetModal.tsx | 4 +- .../SQLEditor/OngoingQueriesPanel.tsx | 2 +- .../SQLEditor/RunQueryWarningModal.tsx | 2 +- .../UtilityPanel/CellDetailPanel.tsx | 2 +- .../SQLEditor/UtilityPanel/ChartConfig.tsx | 4 +- .../SQLEditor/UtilityPanel/Results.tsx | 2 +- .../SQLEditor/UtilityPanel/UtilityPanel.tsx | 6 +- .../UtilityPanel/UtilityTabExplain.tsx | 8 +- .../UtilityPanel/UtilityTabResults.tsx | 8 +- .../Settings/API/ExposedFunctionSelector.tsx | 4 +- .../Settings/API/ExposedSchemaSelector.tsx | 2 +- .../Settings/API/ExposedTableSelector.tsx | 4 +- .../API/UnsafeEntitiesConfirmModal.tsx | 2 +- .../interfaces/Settings/Addons/Addons.tsx | 2 +- .../Settings/Addons/CustomDomainSidePanel.tsx | 6 +- .../Settings/Addons/IPv4SidePanel.tsx | 4 +- .../Settings/Addons/PITRSidePanel.tsx | 6 +- .../Database/DiskSizeConfiguration.tsx | 2 +- .../JitDbAccessRoleGrantFields.tsx | 2 +- .../JitDbAccessRuleSheet.tsx | 2 +- .../CustomDomainActivate.tsx | 2 +- .../CustomDomainConfig/CustomDomainDelete.tsx | 2 +- .../CustomDomainsShimmerLoader.tsx | 4 +- .../DeleteProjectPanel/DeleteProjectModal.tsx | 6 +- .../Infrastructure/RestartServerButton.tsx | 2 +- .../InfrastructureConfiguration/Edge.tsx | 2 +- .../InstanceConfiguration.tsx | 4 +- .../InstanceNode.tsx | 16 +- .../InfrastructureConfiguration/MapView.tsx | 10 +- .../Infrastructure/InfrastructureInfo.tsx | 2 +- .../GitHubIntegrationConnectionForm.tsx | 6 +- .../Integrations/IntegrationsSettings.tsx | 2 +- .../VercelIntegration/VercelSection.tsx | 2 +- .../interfaces/Settings/Logs/LogSelection.tsx | 2 +- .../DefaultPreviewSelectionRenderer.tsx | 4 +- .../interfaces/Settings/Logs/LogTable.tsx | 4 +- .../Settings/Logs/Logs.DatePickers.tsx | 4 +- .../interfaces/Settings/Logs/Logs.Divider.tsx | 2 +- .../Settings/Logs/Logs.SqlSnippetCode.tsx | 2 +- .../Settings/Logs/LogsFilterPopover.tsx | 2 +- .../Settings/Logs/LogsFormatters.tsx | 18 +- .../Settings/Logs/LogsQueryPanel.tsx | 12 +- .../Settings/Logs/LogsTableEmptyState.tsx | 2 +- .../Settings/Logs/PreviewFilterPanel.tsx | 2 +- .../Settings/Logs/RecentQueriesItem.tsx | 2 +- .../Settings/Logs/SidebarV2/SidebarItem.tsx | 2 +- .../Settings/Logs/UpgradePrompt.tsx | 2 +- apps/studio/components/interfaces/Sidebar.tsx | 4 +- .../interfaces/SignIn/LastSignInWrapper.tsx | 2 +- .../interfaces/SignIn/ResetPasswordForm.tsx | 2 +- .../interfaces/SignIn/SignUpForm.tsx | 2 +- .../CreateTableInstructionsDialog.tsx | 6 +- .../CreateTable/CreateTableSheet.tsx | 6 +- .../InitializeForeignSchemaDialog.tsx | 2 +- .../NamespaceWithTables/InsertDataDialog.tsx | 2 +- .../NamespaceWithTables/TableRowComponent.tsx | 4 +- .../UpdateForeignSchemaDialog.tsx | 2 +- .../AnalyticsBucketDetails/index.tsx | 2 +- .../CreateAnalyticsBucketForm.tsx | 6 +- .../interfaces/Storage/EditBucketModal.tsx | 4 +- .../interfaces/Storage/FilesBuckets/index.tsx | 2 +- .../Storage/PublicBucketWarning.tsx | 2 +- .../StorageExplorer/ConfirmDeleteModal.tsx | 2 +- .../StorageExplorer/FileExplorerColumn.tsx | 4 +- .../FileExplorerHeaderSelection.tsx | 2 +- .../StorageExplorer/FileExplorerRow.tsx | 2 +- .../FileExplorerRowEditing.tsx | 2 +- .../Storage/StorageExplorer/PreviewPane.tsx | 2 +- .../Storage/StorageSettings/S3Connection.tsx | 2 +- .../StorageSettings.ValidateSizeLimit.tsx | 2 +- .../CreateVectorBucketDialog.tsx | 2 +- .../VectorBuckets/CreateVectorTableSheet.tsx | 2 +- .../InitializeForeignSchemaDialog.tsx | 2 +- .../VectorBucketTableExamplesSheet.tsx | 2 +- .../interfaces/Support/AIAssistantOption.tsx | 6 +- .../interfaces/Support/AttachmentUpload.tsx | 6 +- .../interfaces/Support/ClientLibraryInfo.tsx | 4 +- .../Support/DashboardLogsToggle.tsx | 2 +- .../interfaces/Support/DocsLink.tsx | 2 +- .../Support/LinkSupportTicketPage.tsx | 2 +- .../interfaces/Support/ProjectAndPlanInfo.tsx | 2 +- .../components/interfaces/Support/Success.tsx | 4 +- .../Support/SupportAccessToggle.tsx | 4 +- .../interfaces/Support/SupportForm.utils.tsx | 8 +- .../interfaces/Support/SupportFormPage.tsx | 8 +- .../DeleteConfirmationDialogs.tsx | 4 +- .../TableGridEditor/GridHeaderActions.tsx | 2 +- .../ColumnEditor/ColumnEditor.tsx | 18 +- .../ColumnEditor/ColumnType.tsx | 2 +- .../ColumnEditor/HeaderTitle.tsx | 6 +- .../ColumnEditor/InputWithSuggestions.tsx | 2 +- .../OperationQueueSidePanel.tsx | 2 +- .../ForeignRowSelector/ForeignRowSelector.tsx | 4 +- .../SidePanelEditor/RowEditor/HeaderTitle.tsx | 2 +- .../DrilldownViewer/DrilldownPane.tsx | 8 +- .../SpreadsheetImport.utils.tsx | 2 +- .../SpreadsheetImportPreview.tsx | 2 +- .../SpreadsheetPreviewGrid.tsx | 2 +- .../SidePanelEditor/TableEditor/Column.tsx | 2 +- .../TableEditor/ColumnManagement.tsx | 2 +- .../ForeignKeysManagement/ForeignKeyRow.tsx | 2 +- .../ForeignKeysManagement.tsx | 2 +- .../TableEditor/HeaderTitle.tsx | 6 +- .../TableEditor/RLSDisableModal.tsx | 2 +- .../TableEditor/TableEditor.tsx | 4 +- .../components/shared/BlockField.tsx | 4 +- .../components/shared/CollapsibleSection.tsx | 2 +- .../components/shared/FieldWithSeeMore.tsx | 6 +- .../components/shared/TimelineStep.tsx | 2 +- .../shared/TruncatedTextWithPopover.tsx | 2 +- .../UnifiedLogs/ServiceFlowPanel.tsx | 2 +- .../interfaces/UnifiedLogs/UnifiedLogs.tsx | 4 +- .../UnifiedLogs/components/LogsListPanel.tsx | 2 +- .../layouts/AppLayout/AppLayout.tsx | 2 +- .../layouts/AppLayout/BranchBadge.tsx | 8 +- .../BranchDropdownCommandContent.tsx | 2 +- .../BranchingPostgresVersionNotice.tsx | 2 +- .../OrganizationDropdownCommandContent.tsx | 2 +- .../EdgeFunctionDetailsLayout.tsx | 4 +- .../Integrations/IntegrationsProductMenu.tsx | 2 +- .../IntegrationWindowLayout.tsx | 2 +- .../Integrations.Commands.tsx | 2 +- .../VercelIntegrationWindowLayout.tsx | 2 +- .../layouts/LogsLayout/LogsSidebarMenuV2.tsx | 4 +- .../FloatingMobileToolbar.tsx | 8 +- .../LayoutHeader/BreadcrumbsView.tsx | 2 +- .../FeedbackDropdown/FeedbackWidget.tsx | 2 +- .../Navigation/LayoutHeader/LayoutHeader.tsx | 2 +- .../LayoutHeader/LocalVersionPopover.tsx | 14 +- .../NavigationBar/CommandOption.tsx | 2 +- .../NavigationBar/MobileNavigationBar.tsx | 4 +- .../NavigationBar/MobileViewNav.tsx | 2 +- .../NavigationBar/NavigationIconButton.tsx | 2 +- .../NavigationBar/NavigationIconLink.tsx | 8 +- .../Navigation/NavigationBar/OrgSelector.tsx | 6 +- .../ProjectBranchSelectorTrigger.tsx | 6 +- .../layouts/Navigation/ProductMenuBar.tsx | 2 +- .../layouts/PageLayout/PageLayout.tsx | 2 +- .../layouts/ProjectIntegrationsLayout.tsx | 2 +- .../layouts/ProjectLayout/ConnectingState.tsx | 4 +- .../MobileMenuContent/MobileMenuContent.tsx | 4 +- .../MobileMenuContent/OrgMenuContent.tsx | 4 +- .../ProjectLayout/LayoutSidebar/index.tsx | 2 +- .../OrganizationSettingsLayout.tsx | 2 +- .../PausedState/PauseDisabledState.tsx | 4 +- .../PausedState/ProjectPausedState.tsx | 2 +- .../layouts/ProjectLayout/PausingState.tsx | 2 +- .../layouts/ProjectLayout/RestoringState.tsx | 2 +- .../ProjectLayout/UpgradingState/index.tsx | 4 +- .../PrivateSqlSnippetEmpty.tsx | 4 +- .../CommunitySnippetsSection.tsx | 2 +- .../SQLEditorNavV2/SQLEditorNav.tsx | 8 +- .../SQLEditorLayout/SqlEditor.Commands.tsx | 4 +- .../TableEditorLayout/EntityListItem.tsx | 2 +- .../TableEditorLayout/TableMenuEmptyState.tsx | 4 +- .../components/layouts/Tabs/ActionCard.tsx | 2 +- .../layouts/Tabs/CollapseButton.tsx | 2 +- .../layouts/Tabs/MigrateToPostgres.tsx | 2 +- .../components/layouts/Tabs/RecentItems.tsx | 2 +- .../components/layouts/Tabs/SortableTab.tsx | 4 +- .../components/layouts/Tabs/TabPreview.tsx | 2 +- apps/studio/components/layouts/Tabs/Tabs.tsx | 10 +- .../layouts/editors/EditorBaseLayout.tsx | 2 +- .../components/to-be-cleaned/ListIcons.tsx | 4 +- .../to-be-cleaned/ProductEmptyState.tsx | 2 +- .../ui/AIAssistantPanel/AIAssistant.tsx | 6 +- .../ui/AIAssistantPanel/AIAssistantHeader.tsx | 2 +- .../ui/AIAssistantPanel/AIOnboarding.tsx | 2 +- .../ui/AIAssistantPanel/AIOptInModal.tsx | 2 +- .../ui/AIAssistantPanel/ConfirmFooter.tsx | 2 +- .../ui/AIAssistantPanel/Message.Actions.tsx | 11 +- .../ui/AIAssistantPanel/Message.Display.tsx | 2 +- .../ui/AIAssistantPanel/Message.Parts.tsx | 2 +- .../ui/AIAssistantPanel/MessageMarkdown.tsx | 4 +- .../ui/AIAssistantPanel/SnippetRow.tsx | 6 +- .../ui/AIAssistantPanel/elements/Tool.tsx | 2 +- apps/studio/components/ui/ActionCard.tsx | 2 +- apps/studio/components/ui/AlphaNotice.tsx | 2 +- .../components/ui/BannerStack/BannerCard.tsx | 2 +- .../Banners/BannerFreeMicroUpgrade.tsx | 4 +- apps/studio/components/ui/CardButton.tsx | 2 +- .../studio/components/ui/Charts/AreaChart.tsx | 2 +- apps/studio/components/ui/Charts/BarChart.tsx | 2 +- .../ui/Charts/ComposedChart.utils.tsx | 2 +- .../components/ui/Charts/StackedBarChart.tsx | 2 +- .../components/ui/CodeEditor/CodeEditor.tsx | 2 +- .../components/ui/ComputeBadgeWrapper.tsx | 4 +- .../DataTableFilterCheckbox.tsx | 10 +- .../DataTableFilterCheckboxAsync.tsx | 10 +- .../DataTableFilterCheckboxLoader.tsx | 6 +- .../DataTableFilterCommand.tsx | 12 +- .../DataTableFilterControls.tsx | 2 +- .../DataTableFilters/DataTableFilterInput.tsx | 2 +- .../DataTableFilterResetButton.tsx | 2 +- .../DataTableFilterSlider.tsx | 4 +- .../ui/DataTable/DataTableInfinite.tsx | 2 +- .../ui/DataTable/DataTableSheetRowAction.tsx | 2 +- .../components/ui/DataTable/LiveRow.tsx | 2 +- apps/studio/components/ui/DataTable/Table.tsx | 8 +- .../DataTable/primitives/InputWithAddons.tsx | 6 +- .../ui/DataTable/primitives/Kbd.tsx | 2 +- .../ui/DataTable/primitives/Slider.tsx | 2 +- .../ui/DatePicker/TimeSplitInput.tsx | 4 +- .../ui/DevToolbar/ResourceWarningsTab.tsx | 2 +- apps/studio/components/ui/DotGrid.tsx | 2 +- .../components/ui/DropdownMenuItemTooltip.tsx | 2 +- .../EdgeFunctionBlock/EdgeFunctionBlock.tsx | 2 +- .../components/ui/EditorPanel/EditorPanel.tsx | 4 +- apps/studio/components/ui/EmptyListState.tsx | 4 +- .../ui/FeaturePreviewSidebarPanel.tsx | 2 +- .../studio/components/ui/Forms/FormHeader.tsx | 2 +- apps/studio/components/ui/Forms/FormPanel.tsx | 2 +- .../components/ui/Forms/FormSection.tsx | 6 +- .../studio/components/ui/FunctionSelector.tsx | 2 +- .../components/ui/GrafanaPromoBanner.tsx | 2 +- .../components/ui/HelpPanel/HelpPanel.tsx | 4 +- .../ui/Logs/ChartIntervalDropdown.tsx | 2 +- .../components/ui/Logs/LogsExplorerHeader.tsx | 12 +- .../ui/OrganizationProjectSelector.tsx | 2 +- apps/studio/components/ui/Panel.tsx | 6 +- .../components/ui/PasswordStrengthBar.tsx | 2 +- .../ui/ProductMenu/ProductMenuItem.tsx | 2 +- .../ui/ProjectSettings/DisplayApiSettings.tsx | 4 +- .../ProjectSettings/DisplayConfigSettings.tsx | 2 +- .../components/ui/QueryBlock/QueryBlock.tsx | 4 +- .../components/ui/Resource/ResourceItem.tsx | 2 +- apps/studio/components/ui/SchemaComboBox.tsx | 4 +- apps/studio/components/ui/SchemaSelector.tsx | 6 +- apps/studio/components/ui/ScrollGradient.tsx | 2 +- apps/studio/components/ui/Shimmers.tsx | 4 +- apps/studio/components/ui/SparkBar.tsx | 6 +- .../components/ui/SqlWarningAdmonition.tsx | 2 +- apps/studio/components/ui/TwoOptionToggle.tsx | 4 +- apps/studio/data/reports/database-charts.ts | 4 +- .../table-rows/table-row-delete-mutation.tsx | 2 +- apps/studio/package.json | 1 - apps/studio/pages/404.tsx | 4 +- apps/studio/pages/_app.tsx | 1 - apps/studio/pages/account/security.tsx | 6 +- .../[slug]/deploy-button/new-project.tsx | 2 +- .../[slug]/marketplace/choose-project.tsx | 2 +- apps/studio/pages/join.tsx | 2 +- .../project/[ref]/advisors/performance.tsx | 2 +- .../pages/project/[ref]/advisors/security.tsx | 2 +- .../project/[ref]/branches/merge-requests.tsx | 2 +- .../[ref]/database/column-privileges.tsx | 4 +- .../[ref]/database/replication/index.tsx | 2 +- .../replication/replica/[replicaId].tsx | 2 +- .../project/[ref]/logs/explorer/templates.tsx | 4 +- .../storage/files/buckets/[bucketId].tsx | 2 +- apps/studio/pages/sign-in-partner.tsx | 6 +- apps/studio/styles/code.css | 4 +- apps/studio/styles/editor.css | 2 + apps/studio/styles/focus.css | 2 + apps/studio/styles/grid.css | 19 +- apps/studio/styles/main.css | 33 +- apps/studio/styles/markdown-preview.css | 1 + apps/studio/styles/monaco.css | 2 + apps/studio/styles/react-data-grid-logs.css | 4 + apps/studio/styles/reactflow.css | 2 + apps/studio/styles/storage.css | 4 +- apps/studio/styles/ui.css | 11 +- apps/studio/tailwind.config.ts | 2 - .../registry/tanstack-db/_generators/list.ts | 2 +- .../registry/tanstack-db/_generators/sheet.ts | 2 +- .../example/password-based-auth/layout.tsx | 2 +- .../app/example/social-auth/layout.tsx | 2 +- .../ui-library/components/block-item-code.tsx | 2 +- apps/ui-library/components/command-menu.tsx | 2 +- apps/ui-library/components/command.tsx | 2 +- .../components/component-preview.tsx | 10 +- apps/ui-library/components/mdx-components.tsx | 15 +- .../components/theme-switcher-dropdown.tsx | 2 +- .../docs/getting-started/quickstart.mdx | 2 +- .../blocks/dropzone/components/dropzone.tsx | 4 +- .../components/avatar-stack.tsx | 2 +- .../realtime-cursor/components/cursor.tsx | 2 +- .../registry/default/components/ui/button.tsx | 10 +- .../registry/default/components/ui/card.tsx | 2 +- .../default/components/ui/checkbox.tsx | 2 +- .../default/components/ui/command.tsx | 6 +- .../registry/default/components/ui/dialog.tsx | 6 +- .../registry/default/components/ui/drawer.tsx | 2 +- .../default/components/ui/hover-card.tsx | 2 +- .../registry/default/components/ui/input.tsx | 2 +- .../default/components/ui/popover.tsx | 2 +- .../registry/default/components/ui/select.tsx | 6 +- .../registry/default/components/ui/switch.tsx | 8 +- .../default/components/ui/textarea.tsx | 2 +- .../default/components/ui/tooltip.tsx | 2 +- .../components/results-table.tsx | 2 +- .../components/supabase-manager/logs.tsx | 4 +- .../supabase-manager/suggestions.tsx | 4 +- apps/ui-library/styles/globals.css | 6 +- apps/ui-library/styles/mdx.css | 2 + apps/ui-library/tailwind.config.js | 3 +- .../_blog/2021-03-25-angels-of-supabase.mdx | 16 +- ...idate-slackbot-to-consolidate-messages.mdx | 2 +- ...trello-board-with-supabase-and-angular.mdx | 22 +- ...023-04-07-designing-with-ai-midjourney.mdx | 2 +- ...dge-runtime-self-hosted-deno-functions.mdx | 2 +- .../2023-04-13-supabase-auth-sso-pkce.mdx | 4 +- ...05-25-chatgpt-plugins-support-postgres.mdx | 2 +- ...023-12-08-how-design-works-at-supabase.mdx | 4 +- .../2024-10-10-database-build-live-share.mdx | 4 +- .../_blog/2024-12-06-database-build-v2.mdx | 2 +- apps/www/_blog/2025-04-04-mcp-server.mdx | 2 +- apps/www/_blog/2025-06-25-natural-db.mdx | 2 +- apps/www/app/blog/BlogClient.tsx | 18 +- .../app/contribute/t/[id]/page-loading.tsx | 16 +- .../StateOfStartups2026Content.tsx | 2 +- .../StateOfStartupsAuroraHeader.tsx | 2 +- .../components/StateOfStartupsHeader.tsx | 2 +- .../components/SurveyChapter.tsx | 2 +- .../components/SurveyChapterSection.tsx | 2 +- .../components/SurveyChart.tsx | 4 +- .../components/SurveyPullQuote.tsx | 2 +- .../components/SurveySectionBreak.tsx | 2 +- .../components/TwoOptionToggle.tsx | 4 +- .../register/RegisterContent.tsx | 8 +- apps/www/app/wrapped/WrappedClient.tsx | 2 +- apps/www/components/AIDemo/DotGrid.tsx | 6 +- apps/www/components/AIDemo/Panel.tsx | 4 +- apps/www/components/AIDemo/SqlSnippet.tsx | 2 +- apps/www/components/Announcement/Badge.tsx | 4 +- apps/www/components/Blog/BlogGridItem.tsx | 4 +- apps/www/components/Blog/BlogPostRenderer.tsx | 2 +- apps/www/components/Blog/DraftModeBanner.tsx | 2 +- apps/www/components/Blog/FeaturedThumb.tsx | 4 +- .../BuiltWithSupabase/ExamplesMobile.tsx | 2 +- .../components/BuiltWithSupabase/index.tsx | 6 +- apps/www/components/Button.tsx | 4 +- .../Changelog/ChangelogTimelineList.tsx | 8 +- .../CodeTabs/CodeTabs.components.tsx | 2 +- .../components/Contribute/Conversation.tsx | 4 +- .../Contribute/SimilarSolvedThreads.tsx | 10 +- .../Contribute/UnansweredThreadsTable.tsx | 6 +- .../Contribute/markdownComponents.tsx | 2 +- .../CustomerStories/CustomersSliderMobile.tsx | 8 +- .../CutomsersSliderDesktop.tsx | 2 +- apps/www/components/CustomerStories/index.tsx | 2 +- apps/www/components/Enterprise/CTAForm.tsx | 2 +- .../components/Enterprise/EnterpriseLogos.tsx | 8 +- .../www/components/Enterprise/Performance.tsx | 4 +- apps/www/components/Enterprise/Security.tsx | 4 +- apps/www/components/Enterprise/UseCases.tsx | 10 +- apps/www/components/EnterpriseFormQuotes.tsx | 4 +- apps/www/components/EventCallout.tsx | 2 +- apps/www/components/Events/EventGridItem.tsx | 4 +- apps/www/components/Events/EventsFilters.tsx | 2 +- .../www/components/Events/new/EventBanner.tsx | 16 +- .../Events/new/EventClientRenderer.tsx | 2 +- .../components/Events/new/EventGallery.tsx | 2 +- apps/www/components/Events/new/EventList.tsx | 14 +- apps/www/components/ExampleCard.tsx | 6 +- .../components/Examples/ExamplesCarousel.tsx | 2 +- .../FloatingIcons/FloatingIcons.tsx | 2 +- apps/www/components/Footer/index.tsx | 8 +- apps/www/components/Go/GoPageRenderer.tsx | 4 +- apps/www/components/Hero/Hero.tsx | 2 +- apps/www/components/Hero/HeroFrameworks.tsx | 4 +- apps/www/components/ImageGrid.tsx | 2 +- .../LaunchWeek/11/HackathonCallout.tsx | 4 +- .../components/LaunchWeek/11/LW11Meetups.tsx | 8 +- .../components/LaunchWeek/11/LW11Summary.tsx | 4 +- .../LaunchWeek/11/LaunchWeekLogoHeader.tsx | 2 +- .../LaunchWeek/11/LaunchWeekPrizeSection.tsx | 2 +- .../LaunchWeek/11/Releases/BuildStage.tsx | 2 +- .../LaunchWeek/11/Releases/LW11Header.tsx | 6 +- .../LaunchWeek/11/Releases/LW11StickyNav.tsx | 10 +- .../LaunchWeek/11/Releases/MainStage.tsx | 4 +- .../11/Releases/components/AdventCard.tsx | 6 +- .../11/Releases/components/DaySection.tsx | 8 +- .../LaunchWeek/12/HackathonCallout.tsx | 4 +- .../LaunchWeek/12/LW12Background.tsx | 2 +- .../components/LaunchWeek/12/LWMeetups.tsx | 4 +- .../LaunchWeek/12/LaunchWeekLogoHeader.tsx | 2 +- .../LaunchWeek/12/LaunchWeekPrizeSection.tsx | 2 +- .../LaunchWeek/12/Releases/BuildStage.tsx | 2 +- .../LaunchWeek/12/Releases/LWHeader.tsx | 4 +- .../LaunchWeek/12/Releases/LWStickyNav.tsx | 8 +- .../LaunchWeek/12/Releases/MainStage.tsx | 2 +- .../12/Releases/components/BuildCard.tsx | 6 +- .../12/Releases/components/DaySection.tsx | 8 +- .../LaunchWeek/13/Releases/BuildStage.tsx | 2 +- .../LaunchWeek/13/Releases/LWHeader.tsx | 2 +- .../LaunchWeek/13/Releases/LWStickyNav.tsx | 6 +- .../LaunchWeek/13/Releases/MainStage.tsx | 2 +- .../13/Releases/components/BuildCard.tsx | 6 +- .../13/Releases/components/DaySection.tsx | 10 +- .../components/LaunchWeek/14/ActionButton.tsx | 18 +- apps/www/components/LaunchWeek/14/Header.tsx | 6 +- .../LaunchWeek/14/Releases/BuildStage.tsx | 2 +- .../LaunchWeek/14/Releases/LWHeader.tsx | 6 +- .../LaunchWeek/14/Releases/LWStickyNav.tsx | 6 +- .../LaunchWeek/14/Releases/MainStage.tsx | 2 +- .../14/Releases/components/BuildCard.tsx | 6 +- .../14/Releases/components/DaySection.tsx | 10 +- .../components/LaunchWeek/15/ActionButton.tsx | 18 +- .../LaunchWeek/15/LW15BuildStage.tsx | 2 +- .../LaunchWeek/15/LW15Hackathon.tsx | 8 +- .../components/LaunchWeek/15/LW15Heading.tsx | 2 +- .../LaunchWeek/15/LW15MainStage.tsx | 20 +- .../components/LaunchWeek/15/LW15Meetups.tsx | 10 +- .../components/LaunchWeek/15/LWSummary.tsx | 2 +- .../LaunchWeek/15/Ticketing/LW15Ticket.tsx | 2 +- .../15/Ticketing/LW15TicketPage.tsx | 2 +- .../LaunchWeek/15/Ticketing/TicketURLCopy.tsx | 4 +- .../LaunchWeek/15/lw15.components.tsx | 2 +- .../components/LaunchWeek/5/ScheduleInfo.tsx | 2 +- .../www/components/LaunchWeek/7/BlogLinks.tsx | 2 +- .../components/LaunchWeek/7/LW7BgGraphic.tsx | 2 +- .../components/LaunchWeek/7/LabelBadge.tsx | 2 +- .../7/LaunchSection/ArticleButton.tsx | 2 +- .../7/LaunchSection/ProductButton.tsx | 2 +- .../7/LaunchSection/ProductButtonListItem.tsx | 2 +- .../LaunchWeek/7/LaunchSection/index.tsx | 4 +- .../LaunchWeek/7/LaunchWeekPrizeCard.tsx | 4 +- .../LaunchWeek/7/LaunchWeekPrizeSection.tsx | 6 +- .../components/LaunchWeek/7/Releases/Day5.tsx | 26 +- .../7/Releases/components/index.tsx | 22 +- .../LaunchWeek/7/Releases/index.tsx | 16 +- .../LaunchWeek/8/LW8CalloutsSection.tsx | 6 +- .../www/components/LaunchWeek/8/LWArchive.tsx | 4 +- .../components/LaunchWeek/8/LabelBadge.tsx | 2 +- .../LaunchWeek/8/LaunchWeekLogoHeader.tsx | 2 +- .../LaunchWeek/8/LaunchWeekPrizeCard.tsx | 4 +- .../LaunchWeek/8/LaunchWeekPrizeSection.tsx | 10 +- .../LaunchWeek/8/Releases/LWAnnouncement.tsx | 4 +- .../8/Releases/components/index.tsx | 22 +- .../LaunchWeek/8/Releases/index.tsx | 2 +- .../components/LaunchWeek/X/Album/Player.tsx | 2 +- .../components/LaunchWeek/X/LWXMeetups.tsx | 2 +- .../components/LaunchWeek/X/LabelBadge.tsx | 2 +- .../LaunchWeek/X/LaunchWeekLogoHeader.tsx | 2 +- .../LaunchWeek/X/Releases/BuildStage.tsx | 2 +- .../LaunchWeek/X/Releases/LWXHeader.tsx | 4 +- .../LaunchWeek/X/Releases/LWXStickyNav.tsx | 8 +- .../LaunchWeek/X/Releases/MainStage.tsx | 6 +- .../X/Releases/components/AdventCard.tsx | 6 +- .../X/Releases/components/DaySection.tsx | 6 +- .../X/Releases/data/lwx_advent_days.tsx | 2 +- apps/www/components/MagnifiedProducts.tsx | 2 +- .../Modules/Cron/CronSQLSection.tsx | 8 +- apps/www/components/Modules/ModulesNav.tsx | 2 +- .../Modules/Queues/QueuesAPISection.tsx | 8 +- .../Modules/Queues/QueuesSQLSection.tsx | 8 +- .../www/components/Nav/DevelopersDropdown.tsx | 8 +- apps/www/components/Nav/GitHubButton.tsx | 2 +- apps/www/components/Nav/HamburgerMenu.tsx | 2 +- apps/www/components/Nav/MenuItem.tsx | 2 +- apps/www/components/Nav/MobileMenu.tsx | 24 +- apps/www/components/Nav/ProductDropdown.tsx | 10 +- .../components/Nav/RightClickBrandLogo.tsx | 4 +- apps/www/components/Nav/SolutionsDropdown.tsx | 2 +- apps/www/components/Nav/index.tsx | 14 +- apps/www/components/NewFeatureCard.tsx | 2 +- apps/www/components/OpenSource/OSSHero.tsx | 10 +- apps/www/components/OpenSource/Repos.tsx | 6 +- apps/www/components/OpenSourceSection.tsx | 2 +- apps/www/components/Panel/Panel.tsx | 6 +- .../components/Partners/PartnerLinkBox.tsx | 2 +- apps/www/components/Partners/TileGrid.tsx | 4 +- apps/www/components/PrevNextFeatureNav.tsx | 2 +- .../Pricing/ComputePricingCalculator.tsx | 6 +- .../Pricing/CostControlAnimation.tsx | 8 +- .../components/Pricing/PricingAddOnTable.tsx | 2 +- apps/www/components/Pricing/PricingAddons.tsx | 2 +- .../Pricing/PricingComputeAnimation.tsx | 12 +- .../Pricing/PricingComputeSection.tsx | 4 +- apps/www/components/Pricing/PricingPlans.tsx | 2 +- .../components/Products/DataAPIsVisual.tsx | 10 +- apps/www/components/Products/Functions/CI.tsx | 6 +- .../Products/Functions/ConnectToDBPanel.tsx | 2 +- .../Products/Functions/FunctionsHero.tsx | 6 +- .../Products/Functions/LocalDXGrid.tsx | 2 +- .../Products/Functions/LocalDXImage.tsx | 2 +- .../components/Products/Functions/Metrics.tsx | 2 +- .../Products/Functions/ParityImage.tsx | 4 +- .../Products/Functions/QueryLogs.tsx | 2 +- .../Functions/TriggerViaWebhookPanel.tsx | 2 +- .../Products/Functions/ZeroConfigPanel.tsx | 2 +- apps/www/components/Products/ProductCard.tsx | 6 +- apps/www/components/Products/ProductsNav.tsx | 2 +- .../components/Products/RealtimeVisual.tsx | 4 +- .../Products/VectorAI/IntegrationsImage.tsx | 2 +- .../Products/VectorAI/OpenAIImage.tsx | 2 +- apps/www/components/Products/VectorVisual.tsx | 6 +- apps/www/components/Products/index.tsx | 6 +- .../components/Realtime/example-layout.tsx | 6 +- .../Realtime/examples/chat-example.tsx | 2 +- .../examples/cursor-tracking-example.tsx | 2 +- .../examples/iframe-annotation-example.tsx | 14 +- .../Realtime/examples/log-viewer-example.tsx | 6 +- .../Realtime/examples/presence-example.tsx | 4 +- .../Realtime/examples/todo-example.tsx | 6 +- apps/www/components/Realtime/sandpack.tsx | 4 +- .../Sections/CenteredTitleImage.tsx | 2 +- .../components/Sections/CommunitySlider.tsx | 4 +- .../Sections/CustomerQuotesSection.tsx | 4 +- .../components/Sections/HighlightCards.tsx | 2 +- .../Sections/ImageParagraphSection.tsx | 4 +- .../components/Sections/ParagraphSection.tsx | 2 +- .../components/Sections/ProductHeader2.tsx | 4 +- .../Sections/ProductHeaderCentered.tsx | 2 +- .../Sections/ProductModulesHeader.tsx | 2 +- apps/www/components/Sections/SingleQuote.tsx | 2 +- .../Sections/TimedAccordionPanels.tsx | 8 +- .../Sections/TimedAccordionSection.tsx | 2 +- .../components/Sections/TimedTabsSection.tsx | 4 +- .../Sections/TwitterSocialProofMobile.tsx | 2 +- .../components/Sections/UseCasesSection.tsx | 2 +- .../components/Solutions/AIBuildersLogos.tsx | 8 +- apps/www/components/Solutions/CTAForm.tsx | 2 +- apps/www/components/Solutions/CtaSection.tsx | 2 +- .../Solutions/DeveloperExperienceSection.tsx | 16 +- apps/www/components/Solutions/FeatureGrid.tsx | 12 +- .../www/components/Solutions/FeaturesGrid.tsx | 4 +- .../components/Solutions/PlatformSection.tsx | 4 +- apps/www/components/Solutions/PostGrid.tsx | 6 +- apps/www/components/Solutions/Quotes.tsx | 4 +- .../components/Solutions/ResultsSection.tsx | 4 +- apps/www/components/Solutions/Videos.tsx | 6 +- apps/www/components/SolutionsStickyNav.tsx | 6 +- .../Supasquad/ApplicationFormSection.tsx | 2 +- .../components/Supasquad/CountrySelector.tsx | 2 +- apps/www/components/Supasquad/CtaSection.tsx | 2 +- .../components/Supasquad/PerfectTiming.tsx | 4 +- apps/www/components/Supasquad/Quotes.tsx | 4 +- .../SurveyResults/StateOfStartupsHeader.tsx | 2 +- .../SurveyResults/SurveyChapter.tsx | 2 +- .../SurveyResults/SurveyChapterSection.tsx | 2 +- .../components/SurveyResults/SurveyChart.tsx | 4 +- .../SurveyResults/SurveyPullQuote.tsx | 2 +- .../SurveyResults/SurveySectionBreak.tsx | 2 +- apps/www/components/Tabs/Tabs.tsx | 2 +- apps/www/components/TabsWithHighlights.tsx | 4 +- apps/www/components/TwitterSocialSection.tsx | 4 +- apps/www/components/UsersGrowthChart.tsx | 6 +- .../Wrapped/AnimatedGridBackground.tsx | 4 +- .../Wrapped/Pages/CustomerStories.tsx | 8 +- apps/www/components/Wrapped/Pages/Devs.tsx | 10 +- apps/www/components/Wrapped/Pages/Home.tsx | 4 +- apps/www/components/Wrapped/Pages/Intro.tsx | 2 +- .../Wrapped/Pages/ProductAnnouncements.tsx | 10 +- .../Wrapped/Pages/SupabaseSelect.tsx | 11 +- .../www/components/Wrapped/Pages/YearOfAI.tsx | 14 +- apps/www/components/logos.tsx | 4 +- apps/www/data/aws-reinvent-2025.tsx | 18 +- apps/www/data/home/content.tsx | 4 +- apps/www/data/products/modules/cron.tsx | 8 +- apps/www/data/products/modules/queues.tsx | 8 +- apps/www/data/solutions/agencies.tsx | 18 +- apps/www/data/solutions/beginners.tsx | 10 +- apps/www/data/solutions/developers.tsx | 18 +- apps/www/data/solutions/firebase.tsx | 20 +- apps/www/data/solutions/hackathon.tsx | 18 +- apps/www/data/solutions/innovation-teams.tsx | 16 +- apps/www/data/solutions/neon.tsx | 20 +- apps/www/data/solutions/no-code.tsx | 24 +- .../data/solutions/postgres-developers.tsx | 36 +- apps/www/data/solutions/startups.tsx | 20 +- apps/www/data/solutions/vibe-coders.tsx | 12 +- apps/www/data/support.tsx | 2 +- apps/www/layouts/comparison.tsx | 2 +- apps/www/pages/404.tsx | 4 +- apps/www/pages/_app.tsx | 2 +- apps/www/pages/assistant.tsx | 4 +- apps/www/pages/auth.tsx | 2 +- apps/www/pages/aws-reinvent-2025.tsx | 2 +- apps/www/pages/beta.tsx | 2 +- apps/www/pages/careers.tsx | 42 +- apps/www/pages/changelog.tsx | 8 +- apps/www/pages/changelog/[slug].tsx | 2 +- apps/www/pages/contact-us.tsx | 4 +- apps/www/pages/contact/sales.tsx | 10 +- apps/www/pages/customers.tsx | 2 +- apps/www/pages/customers/[slug].tsx | 6 +- apps/www/pages/edge-functions.tsx | 6 +- apps/www/pages/events/[slug].tsx | 26 +- apps/www/pages/features.tsx | 24 +- apps/www/pages/features/[slug].tsx | 8 +- apps/www/pages/ga.tsx | 36 +- apps/www/pages/launch-week/12/index.tsx | 2 +- apps/www/pages/launch-week/6/index.tsx | 82 +-- apps/www/pages/launch-week/7/index.tsx | 6 +- apps/www/pages/launch-week/8/index.tsx | 14 +- apps/www/pages/launch-week/ticket.tsx | 2 +- .../pages/launch-week/tickets/[username].tsx | 2 +- apps/www/pages/launch-week/x/index.tsx | 2 +- apps/www/pages/modules/cron.tsx | 4 +- apps/www/pages/modules/queues.tsx | 4 +- apps/www/pages/modules/vector.tsx | 6 +- apps/www/pages/open-source.tsx | 6 +- .../open-source/contributing/supasquad.tsx | 2 +- apps/www/pages/opt-out/[ref].tsx | 2 +- apps/www/pages/partners/index.tsx | 14 +- .../pages/partners/integrations/[slug].tsx | 8 +- apps/www/pages/realtime.tsx | 10 +- apps/www/pages/solutions/agencies.tsx | 2 +- apps/www/pages/solutions/agents.tsx | 4 +- apps/www/pages/solutions/ai-builders.tsx | 2 +- apps/www/pages/solutions/b2b-saas.tsx | 4 +- apps/www/pages/solutions/beginners.tsx | 2 +- apps/www/pages/solutions/developers.tsx | 4 +- apps/www/pages/solutions/enterprise.tsx | 2 +- apps/www/pages/solutions/finserv.tsx | 4 +- apps/www/pages/solutions/healthcare.tsx | 4 +- apps/www/pages/solutions/innovation-teams.tsx | 2 +- apps/www/pages/solutions/no-code.tsx | 2 +- .../pages/solutions/postgres-developers.tsx | 2 +- apps/www/pages/solutions/startups.tsx | 4 +- apps/www/pages/solutions/vibe-coders.tsx | 2 +- apps/www/pages/state-of-startups-2025.tsx | 2 +- apps/www/pages/support.tsx | 10 +- apps/www/styles/career.module.css | 2 + apps/www/styles/index.css | 36 +- apps/www/tailwind.config.js | 18 +- packages/config/package.json | 1 + packages/config/postcss.config.js | 4 +- packages/config/tailwind.config.js | 16 +- packages/config/typography.css | 107 ++-- packages/config/ui.config.js | 5 + packages/dev-tools/DevToolbar.tsx | 18 +- packages/dev-tools/package.json | 2 + packages/marketing/package.json | 10 +- .../src/go/sections/CodeBlockSection.tsx | 6 +- .../src/go/sections/CodeBlockTabs.tsx | 4 +- .../marketing/src/go/sections/FaqSection.tsx | 2 +- .../src/go/sections/FeatureGridSection.tsx | 2 +- .../marketing/src/go/sections/HeroSection.tsx | 2 +- .../src/go/sections/HubSpotMeetingSection.tsx | 2 +- .../src/go/sections/MetricsSection.tsx | 2 +- .../src/go/sections/QuoteSection.tsx | 2 +- .../src/go/sections/SingleColumnSection.tsx | 6 +- .../src/go/sections/StepsSection.tsx | 2 +- .../src/go/sections/ThreeColumnSection.tsx | 4 +- .../src/go/sections/TwoColumnSection.tsx | 4 +- .../hubspot-meeting/TimezoneSelector.tsx | 2 +- packages/ui-patterns/package.json | 8 +- .../src/AssistantChat/AssistantChatForm.tsx | 2 +- .../src/AuthenticatedDropdownMenu/index.tsx | 2 +- .../ui-patterns/src/Banners/LW15Banner.tsx | 2 +- .../src/Chart/charts/chart-bar.tsx | 2 +- .../src/Chart/charts/chart-line.tsx | 2 +- packages/ui-patterns/src/Chart/index.tsx | 4 +- .../ui-patterns/src/CodeBlock/CodeBlock.tsx | 2 +- .../src/CommandMenu/api/Badges.tsx | 2 +- .../src/CommandMenu/api/CommandInput.tsx | 2 +- .../src/CommandMenu/api/CommandMenu.tsx | 8 +- .../src/CommandMenu/internal/Command.tsx | 4 +- .../src/CommandMenu/internal/CommandGroup.tsx | 2 +- .../prepackaged/DocsAi/DocsAiPage.tsx | 16 +- .../prepackaged/DocsSearch/DocsSearchPage.tsx | 14 +- .../CommandMenu/prepackaged/ai/AiWarning.tsx | 2 +- .../ui-patterns/src/ComputeBadge/index.tsx | 2 +- .../src/CountdownWidget/CountdownStep.tsx | 2 +- .../src/Dialogs/ConfirmationModal.tsx | 2 +- .../src/Dialogs/TextConfirmModal.tsx | 2 +- .../src/ErrorDisplay/ErrorDisplay.tsx | 2 +- .../ui-patterns/src/ExpandableVideo/index.tsx | 12 +- .../src/FilterBar/CommandListItem.tsx | 2 +- .../src/FilterBar/FilterCondition.tsx | 8 +- .../ui-patterns/src/FilterBar/FilterGroup.tsx | 4 +- .../src/FilterBar/OperatorSymbolBadge.tsx | 2 +- packages/ui-patterns/src/GlassPanel/index.tsx | 2 +- .../ui-patterns/src/InnerSideMenu/index.tsx | 2 +- .../ui-patterns/src/LogsBarChart/index.tsx | 2 +- .../components/McpConfigurationDisplay.tsx | 5 +- packages/ui-patterns/src/MetricCard/index.tsx | 2 +- packages/ui-patterns/src/PageHeader/index.tsx | 4 +- .../ui-patterns/src/PageSection/index.tsx | 6 +- packages/ui-patterns/src/PopupFrame/index.tsx | 8 +- .../ui-patterns/src/PromoToast/PromoToast.tsx | 2 +- packages/ui-patterns/src/Row/index.tsx | 2 +- .../src/ShimmeringLoader/index.tsx | 2 +- .../ui-patterns/src/SqlToRest/sql-to-rest.tsx | 2 +- packages/ui-patterns/src/TextLink/index.tsx | 2 +- packages/ui-patterns/src/Toc/toc.tsx | 8 +- packages/ui-patterns/src/TweetCard/index.tsx | 2 +- packages/ui-patterns/src/consent.tsx | 4 +- .../src/form/Layout/FormLayout.tsx | 4 +- .../src/multi-select/multi-select.tsx | 14 +- .../ui/src/components/Accordion/Accordion.tsx | 2 +- .../ui/src/components/Alert/Alert.module.css | 2 +- .../AlertCollapsible/AlertCollapsible.tsx | 2 +- packages/ui/src/components/Button/Button.tsx | 2 +- .../ui/src/components/Icon/IconBackground.tsx | 2 +- .../ui/src/components/Input/Input.module.css | 4 +- .../KeyboardShortcut/KeyboardShortcut.tsx | 2 +- .../ui/src/components/Menu/Menu.module.css | 2 +- packages/ui/src/components/Menu/Menu.tsx | 2 +- packages/ui/src/components/NavMenu/index.tsx | 2 +- .../src/components/Popover/Popover.module.css | 4 +- .../src/components/ShadowScrollArea/index.tsx | 16 +- packages/ui/src/components/StatusIcon.tsx | 12 +- .../ui/src/components/radio-group-card.tsx | 6 +- .../ui/src/components/radio-group-stacked.tsx | 8 +- .../src/components/shadcn/ui/alert-dialog.tsx | 4 +- .../ui/src/components/shadcn/ui/alert.tsx | 2 +- .../src/components/shadcn/ui/breadcrumb.tsx | 2 +- .../ui/src/components/shadcn/ui/button.tsx | 2 +- .../ui/src/components/shadcn/ui/calendar.tsx | 4 +- packages/ui/src/components/shadcn/ui/card.tsx | 11 +- .../ui/src/components/shadcn/ui/chart.tsx | 6 +- .../ui/src/components/shadcn/ui/checkbox.tsx | 4 +- .../ui/src/components/shadcn/ui/command.tsx | 14 +- .../src/components/shadcn/ui/context-menu.tsx | 12 +- .../ui/src/components/shadcn/ui/dialog.tsx | 10 +- .../components/shadcn/ui/dropdown-menu.tsx | 14 +- .../ui/src/components/shadcn/ui/field.tsx | 14 +- .../src/components/shadcn/ui/hover-card.tsx | 2 +- .../src/components/shadcn/ui/input-group.tsx | 8 +- .../ui/src/components/shadcn/ui/input-otp.tsx | 5 +- .../ui/src/components/shadcn/ui/input.tsx | 2 +- .../ui/src/components/shadcn/ui/menubar.tsx | 14 +- .../components/shadcn/ui/navigation-menu.tsx | 8 +- .../ui/src/components/shadcn/ui/popover.tsx | 2 +- .../src/components/shadcn/ui/radio-group.tsx | 6 +- .../ui/src/components/shadcn/ui/resizable.tsx | 4 +- .../src/components/shadcn/ui/scroll-area.tsx | 4 +- .../ui/src/components/shadcn/ui/select.tsx | 8 +- .../ui/src/components/shadcn/ui/separator.tsx | 2 +- .../ui/src/components/shadcn/ui/sheet.tsx | 6 +- .../ui/src/components/shadcn/ui/sidebar.tsx | 42 +- .../ui/src/components/shadcn/ui/slider.tsx | 2 +- .../ui/src/components/shadcn/ui/sonner.tsx | 8 +- .../ui/src/components/shadcn/ui/switch.tsx | 6 +- .../ui/src/components/shadcn/ui/table.tsx | 4 +- packages/ui/src/components/shadcn/ui/tabs.tsx | 4 +- .../ui/src/components/shadcn/ui/text-area.tsx | 2 +- .../ui/src/components/shadcn/ui/textarea.tsx | 2 +- .../ui/src/components/shadcn/ui/toggle.tsx | 2 +- packages/ui/src/lib/commonCva.ts | 4 +- packages/ui/src/lib/theme/defaultTheme.ts | 46 +- pnpm-lock.yaml | 470 ++++++++---------- pnpm-workspace.yaml | 16 +- scripts/tw-rename-utilities.ts | 131 +++++ 1137 files changed, 3216 insertions(+), 3102 deletions(-) create mode 100644 scripts/tw-rename-utilities.ts diff --git a/apps/design-system/__registry__/default/block/chart-composed-metrics.tsx b/apps/design-system/__registry__/default/block/chart-composed-metrics.tsx index 361c35a9a45..deab28e4ee2 100644 --- a/apps/design-system/__registry__/default/block/chart-composed-metrics.tsx +++ b/apps/design-system/__registry__/default/block/chart-composed-metrics.tsx @@ -71,7 +71,7 @@ export default function ChartComposedMetrics() { } + loadingState={} > @@ -89,7 +89,7 @@ export default function ChartComposedMetrics() { } + loadingState={} > diff --git a/apps/design-system/app/layout.tsx b/apps/design-system/app/layout.tsx index 143adbc56f9..93fa102345d 100644 --- a/apps/design-system/app/layout.tsx +++ b/apps/design-system/app/layout.tsx @@ -1,5 +1,5 @@ +import 'react-data-grid/lib/styles.css' import '@/styles/globals.css' -import 'config/typography.css' import type { Metadata, Viewport } from 'next' diff --git a/apps/design-system/components/class-label.tsx b/apps/design-system/components/class-label.tsx index 6575c2f4373..a779b1d353e 100644 --- a/apps/design-system/components/class-label.tsx +++ b/apps/design-system/components/class-label.tsx @@ -5,7 +5,7 @@ const ClassLabel = React.forwardRef {children} diff --git a/apps/design-system/components/code-block-wrapper.tsx b/apps/design-system/components/code-block-wrapper.tsx index 2a6c81ce03e..d4be5c110d3 100644 --- a/apps/design-system/components/code-block-wrapper.tsx +++ b/apps/design-system/components/code-block-wrapper.tsx @@ -36,7 +36,7 @@ export function CodeBlockWrapper({
diff --git a/apps/design-system/components/code-fragment.tsx b/apps/design-system/components/code-fragment.tsx index 4e688bf9c28..ccf33703720 100644 --- a/apps/design-system/components/code-fragment.tsx +++ b/apps/design-system/components/code-fragment.tsx @@ -51,7 +51,7 @@ export function CodeFragment({ return (

Code fragment{' '} - + {name} {' '} not found in registry. @@ -94,10 +94,10 @@ export function CodeFragment({ )} > {showGrid && ( -

+
)} {showDottedGrid && ( -
+
)}
{ComponentPreview}
diff --git a/apps/design-system/components/colors.tsx b/apps/design-system/components/colors.tsx index d7ba3a364c6..5a0660ac63a 100644 --- a/apps/design-system/components/colors.tsx +++ b/apps/design-system/components/colors.tsx @@ -70,7 +70,7 @@ const Colors = ({ onClick={() => handleCopy(x, i)} > - + {copiedIndex === i ? 'Copied!' : x} diff --git a/apps/design-system/components/command-menu.tsx b/apps/design-system/components/command-menu.tsx index a2dd7b3de58..fbd26bea2e8 100644 --- a/apps/design-system/components/command-menu.tsx +++ b/apps/design-system/components/command-menu.tsx @@ -56,7 +56,7 @@ export function CommandMenu({ ...props }: DialogProps) { diff --git a/apps/design-system/components/component-preview.tsx b/apps/design-system/components/component-preview.tsx index b6c5005fcb1..0189c7ee1ed 100644 --- a/apps/design-system/components/component-preview.tsx +++ b/apps/design-system/components/component-preview.tsx @@ -55,7 +55,7 @@ export function ComponentPreview({ return (

Component{' '} - + {name} {' '} not found in registry. @@ -99,10 +99,10 @@ export function ComponentPreview({ )} > {showGrid && ( -

+
)} {showDottedGrid && ( -
+
)}
{ComponentPreview}
{/*
*/} @@ -144,10 +144,10 @@ export function ComponentPreview({ })} > {showGrid && ( -
+
)} {showDottedGrid && ( -
+
)}
{ComponentPreview}
@@ -164,12 +164,12 @@ export function ComponentPreview({ px-4 py-4 border border-r group - data-[state=closed]:rounded-bl-md data-[state=closed]:rounded-br-md + data-closed:rounded-bl-md data-closed:rounded-br-md `} > View code diff --git a/apps/design-system/components/grid.tsx b/apps/design-system/components/grid.tsx index 659117a7737..23ca11ac432 100644 --- a/apps/design-system/components/grid.tsx +++ b/apps/design-system/components/grid.tsx @@ -47,8 +47,8 @@ const GridItem = forwardRef>( transition group-hover:border group-hover:border-foreground-muted - group-data-[state=open]:border - group-data-[state=open]:border-foreground-muted + group-data-open:border + group-data-open:border-foreground-muted " >
{children} diff --git a/apps/design-system/components/icons.tsx b/apps/design-system/components/icons.tsx index e94f408759d..ce07e959a04 100644 --- a/apps/design-system/components/icons.tsx +++ b/apps/design-system/components/icons.tsx @@ -18,11 +18,8 @@ function Icons() { - - + + {icon.name} diff --git a/apps/design-system/components/mdx-components.tsx b/apps/design-system/components/mdx-components.tsx index 5e44875ed9e..6d260d75c57 100644 --- a/apps/design-system/components/mdx-components.tsx +++ b/apps/design-system/components/mdx-components.tsx @@ -92,10 +92,7 @@ const components = { /> ), p: ({ className, ...props }: React.HTMLAttributes) => ( -

+

), ul: ({ className, ...props }: React.HTMLAttributes) => (

    @@ -127,7 +124,7 @@ const components = { th: ({ className, ...props }: React.HTMLAttributes) => ( ) => ( ) => ( ) => ( { return null } - const iconClasses = 'text-foreground-lighter group-data-[state=open]:text-foreground' + const iconClasses = 'text-foreground-lighter group-data-open:text-foreground' return ( <> diff --git a/apps/design-system/components/top-navigation.tsx b/apps/design-system/components/top-navigation.tsx index 0af6b28d06b..232167a8528 100644 --- a/apps/design-system/components/top-navigation.tsx +++ b/apps/design-system/components/top-navigation.tsx @@ -13,7 +13,7 @@ export const TopNavigation = () => { const { toggle } = useMobileSidebar() return ( -
    +
-

+

As a result, people stopped telling jokes, and the kingdom fell into a gloom. But there was one person who refused to let the king's foolishness get him down: a court jester named Jokester. @@ -41,12 +41,12 @@ export default function TypographyDemo() {

Jokester's Revolt

-

+

Jokester began sneaking into the castle in the middle of the night and leaving jokes all over the place: under the king's pillow, in his soup, even in the royal toilet. The king was furious, but he couldn't seem to stop Jokester.

-

+

And then, one day, the people of the kingdom discovered that the jokes left by Jokester were so funny that they couldn't help but laugh. And once they started laughing, they couldn't stop. @@ -54,7 +54,7 @@ export default function TypographyDemo() {

The People's Rebellion

-

+

The people of the kingdom, feeling uplifted by the laughter, started to tell jokes and puns again, and soon the entire kingdom was in on the joke.

@@ -62,48 +62,48 @@ export default function TypographyDemo() { - - - - - - - -
+ King's Treasury + People's happiness
+ Empty + Overflowing
+ Modest + Satisfied
+ Full + Ecstatic
-

+

The king, seeing how much happier his subjects were, realized the error of his ways and repealed the joke tax. Jokester was declared a hero, and the kingdom lived happily ever after.

-

+

The moral of the story is: never underestimate the power of a good laugh and always be careful of bad ideas.

diff --git a/apps/design-system/registry/default/example/typography-inline-code.tsx b/apps/design-system/registry/default/example/typography-inline-code.tsx index fbbc96ba688..36e5b271396 100644 --- a/apps/design-system/registry/default/example/typography-inline-code.tsx +++ b/apps/design-system/registry/default/example/typography-inline-code.tsx @@ -1,6 +1,6 @@ export default function TypographyInlineCode() { return ( - + @radix-ui/react-alert-dialog ) diff --git a/apps/design-system/registry/default/example/typography-p.tsx b/apps/design-system/registry/default/example/typography-p.tsx index 0a20c523c48..ed5f1131101 100644 --- a/apps/design-system/registry/default/example/typography-p.tsx +++ b/apps/design-system/registry/default/example/typography-p.tsx @@ -1,6 +1,6 @@ export default function TypographyP() { return ( -

+

The king, seeing how much happier his subjects were, realized the error of his ways and repealed the joke tax.

diff --git a/apps/design-system/registry/default/example/typography-table.tsx b/apps/design-system/registry/default/example/typography-table.tsx index 7a91dfda844..a24e2c5316e 100644 --- a/apps/design-system/registry/default/example/typography-table.tsx +++ b/apps/design-system/registry/default/example/typography-table.tsx @@ -4,36 +4,36 @@ export default function TypographyTable() { - - - - - - - - diff --git a/apps/design-system/styles/globals.css b/apps/design-system/styles/globals.css index d1f98daa8a0..0160d13280c 100644 --- a/apps/design-system/styles/globals.css +++ b/apps/design-system/styles/globals.css @@ -1,11 +1,13 @@ +@import 'tailwindcss'; + @import './../../../packages/ui/build/css/source/global.css'; @import './../../../packages/ui/build/css/themes/dark.css'; @import './../../../packages/ui/build/css/themes/classic-dark.css'; @import './../../../packages/ui/build/css/themes/light.css'; -@tailwind base; -@tailwind components; -@tailwind utilities; +@config '../tailwind.config.js'; + +@import 'config/typography.css'; @layer base { :root { @@ -68,3 +70,7 @@ @apply px-4; } } + +.rdg-cell { + padding-inline: 0.5rem; +} diff --git a/apps/design-system/styles/mdx.css b/apps/design-system/styles/mdx.css index 84391167b6f..32f049edca0 100644 --- a/apps/design-system/styles/mdx.css +++ b/apps/design-system/styles/mdx.css @@ -1,3 +1,5 @@ +@reference './globals.css'; + /* [data-theme="light"] { display: block; } */ diff --git a/apps/design-system/tailwind.config.js b/apps/design-system/tailwind.config.js index 4d875930e75..e329c8e381e 100644 --- a/apps/design-system/tailwind.config.js +++ b/apps/design-system/tailwind.config.js @@ -5,12 +5,12 @@ module.exports = config({ './app/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}', './registry/**/*.{js,ts,jsx,tsx}', + './content/**/*.mdx', // purge styles from grid library // './../../packages/ui/src/**/*.{tsx,ts,js}', './../../packages/ui-patterns/src/**/*.{tsx,ts,js}', ], - plugins: [require('@tailwindcss/container-queries')], theme: { extend: { maxWidth: { diff --git a/apps/docs/app/graphiql/page.tsx b/apps/docs/app/graphiql/page.tsx index 671b5525963..377685efd69 100644 --- a/apps/docs/app/graphiql/page.tsx +++ b/apps/docs/app/graphiql/page.tsx @@ -29,5 +29,5 @@ export default function GraphiQLPage() { notFound() } - return + return } diff --git a/apps/docs/app/guides/database/database-advisors/page.tsx b/apps/docs/app/guides/database/database-advisors/page.tsx index 334be03cd77..889dff9b749 100644 --- a/apps/docs/app/guides/database/database-advisors/page.tsx +++ b/apps/docs/app/guides/database/database-advisors/page.tsx @@ -87,7 +87,7 @@ const DatabaseAdvisorDocs = async () => { . ) : ( - + {lints.map((lint) => ( {nestedAttribute}
    -
  • +
  • { schema[resource].block.attributes[attribute].nested_type @@ -285,7 +285,7 @@ function DataSources({ schema }: { schema: any }) {
  • {nestedAttribute}
      -
    • +
    • { schema[dataSource].block.attributes[attribute].nested_type diff --git a/apps/docs/app/guides/troubleshooting/page.tsx b/apps/docs/app/guides/troubleshooting/page.tsx index 74582ef4838..d00356c6e27 100644 --- a/apps/docs/app/guides/troubleshooting/page.tsx +++ b/apps/docs/app/guides/troubleshooting/page.tsx @@ -26,7 +26,7 @@ export default async function GlobalTroubleshootingPage() { const errors = await getAllTroubleshootingErrors() return ( - +

      Troubleshooting

      diff --git a/apps/docs/components/AuthSmsProviderConfig/AuthSmsProviderConfig.tsx b/apps/docs/components/AuthSmsProviderConfig/AuthSmsProviderConfig.tsx index 357b37ffa77..9e634a825d2 100644 --- a/apps/docs/components/AuthSmsProviderConfig/AuthSmsProviderConfig.tsx +++ b/apps/docs/components/AuthSmsProviderConfig/AuthSmsProviderConfig.tsx @@ -62,18 +62,18 @@ const AuthSmsProviderConfig = () => { > {selectedProvider && ( { evt.preventDefault() headingRef.current?.focus() }} > - + {selectedProvider.name} - + {selectedProvider.name.toLowerCase().includes('messagebird') && } {selectedProvider.name.toLowerCase().includes('twilio') && } {selectedProvider.name.toLowerCase().includes('vonage') && } diff --git a/apps/docs/components/BodyContentTypeDropdown.tsx b/apps/docs/components/BodyContentTypeDropdown.tsx index 46c5437272e..33334ae094d 100644 --- a/apps/docs/components/BodyContentTypeDropdown.tsx +++ b/apps/docs/components/BodyContentTypeDropdown.tsx @@ -26,7 +26,7 @@ const BodyContentTypeDropdown = ({ types, onSelect }: IParamProps) => { border hover:border-control hover:bg-overlay-hover - border-control px-2 h-[32px] rounded + border-control px-2 h-[32px] rounded-sm font-mono flex items-center gap-1 text-foreground text-sm group-hover:text-foreground transition diff --git a/apps/docs/components/ButtonCard.tsx b/apps/docs/components/ButtonCard.tsx index ecc50fd15c0..f7df15298f3 100644 --- a/apps/docs/components/ButtonCard.tsx +++ b/apps/docs/components/ButtonCard.tsx @@ -26,7 +26,7 @@ const ButtonCard: FC = ({

      -
      +
      diff --git a/apps/docs/components/Navigation/NavigationMenu/GlobalNavigationMenu.tsx b/apps/docs/components/Navigation/NavigationMenu/GlobalNavigationMenu.tsx index 29445946b4d..f323b7737a7 100644 --- a/apps/docs/components/Navigation/NavigationMenu/GlobalNavigationMenu.tsx +++ b/apps/docs/components/Navigation/NavigationMenu/GlobalNavigationMenu.tsx @@ -59,7 +59,7 @@ export const useActiveMenuLabel = (menu: typeof GLOBAL_MENU_ITEMS) => { const GlobalNavigationMenu: FC = () => { const activeLabel = useActiveMenuLabel(GLOBAL_MENU_ITEMS) const triggerClassName = - 'h-[var(--header-height)] p-2 bg-transparent border-0 border-b-2 border-transparent font-normal rounded-none text-foreground-light hover:text-foreground data-[state=open]:!text-foreground data-[radix-collection-item]:focus-visible:ring-2 data-[radix-collection-item]:focus-visible:ring-foreground-lighter data-[radix-collection-item]:focus-visible:text-foreground h-full focus-visible:rounded !shadow-none outline-none transition-all outline-0 focus-visible:outline-4 focus-visible:outline-offset-1 focus-visible:outline-brand-600' + 'h-(--header-height) p-2 bg-transparent border-0 border-b-2 border-transparent font-normal rounded-none text-foreground-light hover:text-foreground data-open:text-foreground! data-radix-collection-item:focus-visible:ring-2 data-radix-collection-item:focus-visible:ring-foreground-lighter data-radix-collection-item:focus-visible:text-foreground h-full focus-visible:rounded-sm shadow-none! outline-hidden transition-all outline-0 focus-visible:outline-4 focus-visible:outline-offset-1 focus-visible:outline-brand-600' return (
      @@ -68,9 +68,9 @@ const GlobalNavigationMenu: FC = () => { skipDelayDuration={0} className="w-full flex justify-start h-full" renderViewport={false} - viewportClassName="mt-0 max-w-screen overflow-hidden border-0 rounded-none mt-1.5 rounded-md !border-x" + viewportClassName="mt-0 max-w-screen overflow-hidden border-0 rounded-none mt-1.5 rounded-md border-x!" > - + {GLOBAL_MENU_ITEMS.filter((section) => section[0].enabled !== false).map( (section, sectionIdx) => section[0].menuItems ? ( @@ -91,7 +91,7 @@ const GlobalNavigationMenu: FC = () => { section[0].label )} - +
      {section[0].menuItems?.map((menuItem, menuItemIndex) => ( {

      diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenuRefListItems.tsx b/apps/docs/components/Navigation/NavigationMenu/NavigationMenuRefListItems.tsx index 5f4a5836cd3..18e8d40f6cb 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenuRefListItems.tsx +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenuRefListItems.tsx @@ -78,7 +78,7 @@ const FunctionLink = memo(function FunctionLink({ {active && !isSubItem && ( )} {isParent && ( diff --git a/apps/docs/components/Navigation/NavigationMenu/TopNavBar.tsx b/apps/docs/components/Navigation/NavigationMenu/TopNavBar.tsx index 66221d9e786..ed7537aab81 100644 --- a/apps/docs/components/Navigation/NavigationMenu/TopNavBar.tsx +++ b/apps/docs/components/Navigation/NavigationMenu/TopNavBar.tsx @@ -32,9 +32,9 @@ const TopNavBar: FC = () => { <>

+ King's Treasury + People's happiness
+ Empty + Overflowing
+ Modest + Satisfied
+ Full + Ecstatic
) => ( { ) } - const iconClasses = 'text-foreground-light group-data-[state=open]:text-foreground' + const iconClasses = 'text-foreground-light group-data-open:text-foreground' return ( <> diff --git a/apps/learn/components/what-will-i-learn.tsx b/apps/learn/components/what-will-i-learn.tsx index 4be8a0522c2..178bbf07318 100644 --- a/apps/learn/components/what-will-i-learn.tsx +++ b/apps/learn/components/what-will-i-learn.tsx @@ -100,19 +100,19 @@ export function WhatWillILearn() {
    -
  • +
  • Real-time room monitoring that tracks live occupancy, sensor readings
  • -
  • +
  • Booking management with live status filters, calendar integrations.
  • -
  • +
  • @@ -120,7 +120,7 @@ export function WhatWillILearn() { Advanced analytics that surface 30+ days of utilization trends and patterns.
  • -
  • +
  • diff --git a/apps/learn/package.json b/apps/learn/package.json index 1f75dbc32e4..217c202dc5b 100644 --- a/apps/learn/package.json +++ b/apps/learn/package.json @@ -43,8 +43,9 @@ "@types/react": "catalog:", "config": "workspace:^", "mdast-util-toc": "^6.1.1", + "shiki": "^1.1.7", + "tailwindcss": "catalog:", "tsconfig": "workspace:*", - "tsx": "catalog:", - "shiki": "^1.1.7" + "tsx": "catalog:" } } diff --git a/apps/learn/styles/globals.css b/apps/learn/styles/globals.css index 6f47ae5da96..54735e85e71 100644 --- a/apps/learn/styles/globals.css +++ b/apps/learn/styles/globals.css @@ -1,11 +1,11 @@ +@import 'tailwindcss'; + @import './../../../packages/ui/build/css/source/global.css'; @import './../../../packages/ui/build/css/themes/dark.css'; @import './../../../packages/ui/build/css/themes/classic-dark.css'; @import './../../../packages/ui/build/css/themes/light.css'; -@tailwind base; -@tailwind components; -@tailwind utilities; +@config '../tailwind.config.js'; @layer base { :root { diff --git a/apps/learn/styles/mdx.css b/apps/learn/styles/mdx.css index 66ebc271c5c..8b06fb2d87f 100644 --- a/apps/learn/styles/mdx.css +++ b/apps/learn/styles/mdx.css @@ -1,3 +1,5 @@ +@reference './globals.css'; + .dark [data-theme='light'] { display: none; } diff --git a/apps/lite-studio/app/app.css b/apps/lite-studio/app/app.css index 67093bff5cd..da69bb81ee1 100644 --- a/apps/lite-studio/app/app.css +++ b/apps/lite-studio/app/app.css @@ -1,8 +1,12 @@ +@import 'tailwindcss'; + @import './../../../packages/ui/build/css/source/global.css'; @import './../../../packages/ui/build/css/themes/dark.css'; @import './../../../packages/ui/build/css/themes/light.css'; -@tailwind components; +@config '../tailwind.config.js'; + +@import 'config/typography.css'; @font-face { font-family: 'custom-font'; diff --git a/apps/lite-studio/app/root.tsx b/apps/lite-studio/app/root.tsx index 73dda59651e..6ea72dfb0f6 100644 --- a/apps/lite-studio/app/root.tsx +++ b/apps/lite-studio/app/root.tsx @@ -3,7 +3,6 @@ import { isRouteErrorResponse, Links, Meta, Outlet, Scripts, ScrollRestoration } import type { Route } from './+types/root' import './app.css' -import 'config/typography.css' export const links: Route.LinksFunction = () => [ { rel: 'preconnect', href: 'https://fonts.googleapis.com' }, diff --git a/apps/lite-studio/package.json b/apps/lite-studio/package.json index 7429825bb70..54379669df7 100644 --- a/apps/lite-studio/package.json +++ b/apps/lite-studio/package.json @@ -25,7 +25,6 @@ "@types/node": "^22", "@types/react": "catalog:", "@types/react-dom": "catalog:", - "autoprefixer": "^10.4.27", "config": "workspace:*", "postcss": "catalog:", "tailwindcss": "catalog:", diff --git a/apps/studio/components/grid/components/editor/DateTimeEditor.tsx b/apps/studio/components/grid/components/editor/DateTimeEditor.tsx index 8f0e8f4ac33..321308e17c9 100644 --- a/apps/studio/components/grid/components/editor/DateTimeEditor.tsx +++ b/apps/studio/components/grid/components/editor/DateTimeEditor.tsx @@ -93,7 +93,7 @@ function BaseEditor({ value={inputValue ?? ''} placeholder={format} onChange={(e) => setInputValue(e.target.value)} - className="border-0 rounded-none bg-dash-sidebar outline-none !ring-0 !ring-offset-0" + className="border-0 rounded-none bg-dash-sidebar outline-hidden ring-0! ring-offset-0!" />
    @@ -107,7 +107,7 @@ function BaseEditor({ displayAs="utc" utcTimestamp={timeValue} labelFormat="DD MMM YYYY HH:mm:ss (ZZ)" - className="text-left !text-sm font-mono tracking-tight" + className="text-left text-sm! font-mono tracking-tight" /> ) : (

    @@ -126,13 +126,13 @@ function BaseEditor({

    -
    +
    ⏎

    {applyChangesLabel}

    -
    +
    Esc

    Cancel changes

    diff --git a/apps/studio/components/grid/components/editor/JsonEditor.tsx b/apps/studio/components/grid/components/editor/JsonEditor.tsx index da6ed1e8162..58822f49b77 100644 --- a/apps/studio/components/grid/components/editor/JsonEditor.tsx +++ b/apps/studio/components/grid/components/editor/JsonEditor.tsx @@ -187,13 +187,13 @@ export const JsonEditor = ({ {isEditable && (
    -
    +
    ⏎

    {applyChangesLabel}

    -
    +
    Esc

    Cancel changes

    @@ -204,7 +204,7 @@ export const JsonEditor = ({
    onSelectExpand()} diff --git a/apps/studio/components/grid/components/editor/TextEditor.tsx b/apps/studio/components/grid/components/editor/TextEditor.tsx index d719cf39074..494adf84018 100644 --- a/apps/studio/components/grid/components/editor/TextEditor.tsx +++ b/apps/studio/components/grid/components/editor/TextEditor.tsx @@ -143,13 +143,13 @@ export const TextEditor = ({
    -
    +
    ⏎

    {applyChangesLabel}

    -
    +
    Esc

    Cancel changes

    diff --git a/apps/studio/components/grid/components/formatter/ReferenceRecordPeek.tsx b/apps/studio/components/grid/components/formatter/ReferenceRecordPeek.tsx index 8fb5bf1bd44..0d7e1043991 100644 --- a/apps/studio/components/grid/components/formatter/ReferenceRecordPeek.tsx +++ b/apps/studio/components/grid/components/formatter/ReferenceRecordPeek.tsx @@ -80,7 +80,7 @@ export const ReferenceRecordPeek = ({ table, column, value }: ReferenceRecordPee sortable: false, width: columnWidth, minWidth: COLUMN_MIN_WIDTH, - headerCellClass: 'outline-none !shadow-none', + headerCellClass: 'outline-hidden shadow-none!', renderHeaderCell: () => (
    {isPrimaryKey && ( diff --git a/apps/studio/components/grid/components/grid/ColumnHeader.tsx b/apps/studio/components/grid/components/grid/ColumnHeader.tsx index 30808cbec6c..cc75818f6f8 100644 --- a/apps/studio/components/grid/components/grid/ColumnHeader.tsx +++ b/apps/studio/components/grid/components/grid/ColumnHeader.tsx @@ -88,7 +88,7 @@ export function ColumnHeader({ onClick={() => openSheet(column.name as string)} > View {column.name} index suggestion - + @@ -124,20 +124,20 @@ function ColumnIcon({

    Foreign key relation:

    -

    {name}

    - -

    +

    {name}

    + +

    {foreignKey?.targetTableSchema}.{foreignKey?.targetTableName}. {foreignKey?.targetColumnName}

    {foreignKey?.updateAction !== FOREIGN_KEY_CASCADE_ACTION.NO_ACTION && ( -

    +

    On update: {getForeignKeyCascadeAction(foreignKey?.updateAction)}

    )} {foreignKey?.deletionAction !== FOREIGN_KEY_CASCADE_ACTION.NO_ACTION && ( -

    +

    On delete: {getForeignKeyCascadeAction(foreignKey?.deletionAction)}

    )} diff --git a/apps/studio/components/grid/components/grid/ColumnOverlayItem.tsx b/apps/studio/components/grid/components/grid/ColumnOverlayItem.tsx index e5cd5bbe03c..f48b9cedb47 100644 --- a/apps/studio/components/grid/components/grid/ColumnOverlayItem.tsx +++ b/apps/studio/components/grid/components/grid/ColumnOverlayItem.tsx @@ -34,7 +34,7 @@ export function ColumnOverlayItem({ column }: ColumnOverlayItemProps) { {columnFormat === 'bytea' ? ` (hex)` : ''} {column.isEncrypted && } - {hasIndexSuggestion && } + {hasIndexSuggestion && }
    ) diff --git a/apps/studio/components/grid/components/grid/Grid.tsx b/apps/studio/components/grid/components/grid/Grid.tsx index 4377e34d5ef..f714cf5d01d 100644 --- a/apps/studio/components/grid/components/grid/Grid.tsx +++ b/apps/studio/components/grid/components/grid/Grid.tsx @@ -231,7 +231,7 @@ export const Grid = memo( {(rows ?? []).length === 0 && (
    {isLoading && !isDisabled && ( diff --git a/apps/studio/components/grid/components/grid/GridError.tsx b/apps/studio/components/grid/components/grid/GridError.tsx index 1574b239fd4..cdedbab22f1 100644 --- a/apps/studio/components/grid/components/grid/GridError.tsx +++ b/apps/studio/components/grid/components/grid/GridError.tsx @@ -121,7 +121,7 @@ const FilterError = ({ removeAllFilters }: { removeAllFilters: () => void }) => className="pointer-events-auto" title="No results found — check your filter values" > -

    +

    One or more of your filters may have a value or operator that doesn't match the column's data type. Try updating or removing the filter.

    @@ -145,16 +145,16 @@ const InvalidOrderingOperatorError = ({ error }: { error: ResponseError }) => { className="pointer-events-auto" title={`Sorting is not supporting on ${sorts.length > 1 ? 'one of the selected columns' : 'the selected column'}`} > -

    +

    Unable to retrieve results as sorting is not supported on{' '} {sorts.length > 1 ? 'one of the selected columns' : 'the selected column'} due to its data type. ({formattedInvalidDataType})

    -

    +

    Remove any sorts on columns with the data type {formattedInvalidDataType} applying the sorts again.

    -

    +

    Error: {error.message}

    diff --git a/apps/studio/components/grid/components/header/Header.tsx b/apps/studio/components/grid/components/header/Header.tsx index c5cfec42e8c..20086e61688 100644 --- a/apps/studio/components/grid/components/header/Header.tsx +++ b/apps/studio/components/grid/components/header/Header.tsx @@ -145,7 +145,7 @@ const InsertButton = () => {
    @@ -169,7 +169,7 @@ const InsertButton = () => {
    @@ -204,11 +204,11 @@ const InsertButton = () => { }} >
    - + { }, [getRowAndColumn]) return ( - + Copy cell diff --git a/apps/studio/components/grid/context/TableIndexAdvisorContext.tsx b/apps/studio/components/grid/context/TableIndexAdvisorContext.tsx index c12f949293f..09d986de1c9 100644 --- a/apps/studio/components/grid/context/TableIndexAdvisorContext.tsx +++ b/apps/studio/components/grid/context/TableIndexAdvisorContext.tsx @@ -128,7 +128,7 @@ export function TableIndexAdvisorProvider({ {children} !open && closeSheet()}> - + Index Recommendation diff --git a/apps/studio/components/interfaces/APIKeys/APIKeyRow.tsx b/apps/studio/components/interfaces/APIKeys/APIKeyRow.tsx index 91115ebde06..764bad7a475 100644 --- a/apps/studio/components/interfaces/APIKeys/APIKeyRow.tsx +++ b/apps/studio/components/interfaces/APIKeys/APIKeyRow.tsx @@ -88,7 +88,7 @@ export const APIKeyRow = ({
    - + - + diff --git a/apps/studio/components/interfaces/Account/AccessTokens/AccessTokenTable/TableContainer.tsx b/apps/studio/components/interfaces/Account/AccessTokens/AccessTokenTable/TableContainer.tsx index 8462711f9fd..e684a587677 100644 --- a/apps/studio/components/interfaces/Account/AccessTokens/AccessTokenTable/TableContainer.tsx +++ b/apps/studio/components/interfaces/Account/AccessTokens/AccessTokenTable/TableContainer.tsx @@ -35,7 +35,7 @@ export const TableContainer = ({ children, sort, onSortChange }: TableContainerP Expires - + {children} diff --git a/apps/studio/components/interfaces/Account/AccessTokens/Scoped/NewScopedTokenSheet.tsx b/apps/studio/components/interfaces/Account/AccessTokens/Scoped/NewScopedTokenSheet.tsx index cc9a391d7dd..7943c87529b 100644 --- a/apps/studio/components/interfaces/Account/AccessTokens/Scoped/NewScopedTokenSheet.tsx +++ b/apps/studio/components/interfaces/Account/AccessTokens/Scoped/NewScopedTokenSheet.tsx @@ -247,7 +247,7 @@ export const NewScopedTokenSheet = ({ @@ -312,7 +312,7 @@ export const NewScopedTokenSheet = ({
    - +
    - + {(organizations ?? []).length > 0 ? ( <> diff --git a/apps/studio/components/interfaces/Account/TOTPFactors/AddNewFactorModal.tsx b/apps/studio/components/interfaces/Account/TOTPFactors/AddNewFactorModal.tsx index 1e0b9143c98..f9d6e06f927 100644 --- a/apps/studio/components/interfaces/Account/TOTPFactors/AddNewFactorModal.tsx +++ b/apps/studio/components/interfaces/Account/TOTPFactors/AddNewFactorModal.tsx @@ -217,7 +217,7 @@ const SecondStep = ({ {factor && (
    -
    +
    {factor.totp.uri}
    diff --git a/apps/studio/components/interfaces/Advisors/AdvisorRuleItem.tsx b/apps/studio/components/interfaces/Advisors/AdvisorRuleItem.tsx index 8f6afca5b60..62dbabdeb9d 100644 --- a/apps/studio/components/interfaces/Advisors/AdvisorRuleItem.tsx +++ b/apps/studio/components/interfaces/Advisors/AdvisorRuleItem.tsx @@ -106,7 +106,7 @@ export const AdvisorRuleItem = ({ lint }: AdvisorRuleItemProps) => { else setExpandedLint(undefined) }} > - +
    @@ -138,7 +138,7 @@ export const AdvisorRuleItem = ({ lint }: AdvisorRuleItemProps) => { 0 ? 'divide-y' : '' )} > diff --git a/apps/studio/components/interfaces/Advisors/AdvisorRules.tsx b/apps/studio/components/interfaces/Advisors/AdvisorRules.tsx index 72c59071b51..fe4056a3b64 100644 --- a/apps/studio/components/interfaces/Advisors/AdvisorRules.tsx +++ b/apps/studio/components/interfaces/Advisors/AdvisorRules.tsx @@ -11,7 +11,7 @@ export const AdvisorRules = ({ category }: AdvisorRulesProps) => { return ( - +
    {lints.map((lint) => ( diff --git a/apps/studio/components/interfaces/Advisors/AdvisorRules.utils.tsx b/apps/studio/components/interfaces/Advisors/AdvisorRules.utils.tsx index 62ff6b1e4d7..786a34457d6 100644 --- a/apps/studio/components/interfaces/Advisors/AdvisorRules.utils.tsx +++ b/apps/studio/components/interfaces/Advisors/AdvisorRules.utils.tsx @@ -25,7 +25,7 @@ export const generateRuleDescription = ({ return ( <>

    What this rule means:

    -

    +

    The "{lint?.title}" lint will be{' '} {disabled ? `ignored for ${!!member ? `this user only` : 'this project'}` diff --git a/apps/studio/components/interfaces/App/CommandMenu/ContextSearchResults.shared.tsx b/apps/studio/components/interfaces/App/CommandMenu/ContextSearchResults.shared.tsx index 2db9db46f0e..d2ae7fd773b 100644 --- a/apps/studio/components/interfaces/App/CommandMenu/ContextSearchResults.shared.tsx +++ b/apps/studio/components/interfaces/App/CommandMenu/ContextSearchResults.shared.tsx @@ -18,10 +18,10 @@ export function SkeletonResults() {

    {[0, 1, 2, 3].map((i) => (
    - +
    - - + +
    ))} @@ -88,7 +88,7 @@ export function ResultsList({ return ( diff --git a/apps/studio/components/interfaces/App/CommandMenu/CreateCommands.tsx b/apps/studio/components/interfaces/App/CommandMenu/CreateCommands.tsx index 2a91347b5a5..29e4c0b8898 100644 --- a/apps/studio/components/interfaces/App/CommandMenu/CreateCommands.tsx +++ b/apps/studio/components/interfaces/App/CommandMenu/CreateCommands.tsx @@ -330,7 +330,7 @@ export function useCreateCommands(options?: CommandOptions) { const getIcon = () => { if (isWrapper) { return ( -
    +
    {integration.icon()}
    ) diff --git a/apps/studio/components/interfaces/App/FeaturePreview/AdvisorRulesPreview.tsx b/apps/studio/components/interfaces/App/FeaturePreview/AdvisorRulesPreview.tsx index 2a96db1432a..1f4d3964817 100644 --- a/apps/studio/components/interfaces/App/FeaturePreview/AdvisorRulesPreview.tsx +++ b/apps/studio/components/interfaces/App/FeaturePreview/AdvisorRulesPreview.tsx @@ -21,9 +21,9 @@ export const AdvisorRulesPreview = () => { width={1296} height={900} alt="api-docs-side-panel-preview" - className="rounded border mb-4" + className="rounded-sm border mb-4" /> -
    +

    Enabling this preview will:

    • diff --git a/apps/studio/components/interfaces/App/FeaturePreview/CLSPreview.tsx b/apps/studio/components/interfaces/App/FeaturePreview/CLSPreview.tsx index e7064826eae..672f6bf0c6c 100644 --- a/apps/studio/components/interfaces/App/FeaturePreview/CLSPreview.tsx +++ b/apps/studio/components/interfaces/App/FeaturePreview/CLSPreview.tsx @@ -41,9 +41,9 @@ export const CLSPreview = () => { width={1860} height={970} alt="api-docs-side-panel-preview" - className="rounded border" + className="rounded-sm border" /> -
      +

      Enabling this preview will:

      • diff --git a/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreviewModal.tsx b/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreviewModal.tsx index ae16a7b11a1..01cdada28fd 100644 --- a/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreviewModal.tsx +++ b/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreviewModal.tsx @@ -96,7 +96,7 @@ export const FeaturePreviewModal = () => { return ( - + Dashboard feature previews Get early access to new features and give feedback @@ -104,7 +104,7 @@ export const FeaturePreviewModal = () => { - + {allFeaturePreviews.length > 0 ? (
        @@ -136,7 +136,7 @@ export const FeaturePreviewModal = () => { {selectedFeature.isNew && New}
        - + {allFeaturePreviews.map((feature) => ( selectFeaturePreview(feature.key)} className={cn( - '!w-full flex-1 flex items-center justify-between p-4 border-b cursor-pointer bg transition', + 'w-full! flex-1 flex items-center justify-between p-4 border-b cursor-pointer bg transition', selectedFeature?.key === feature.key ? 'bg-surface-300' : 'bg-surface-100', className )} diff --git a/apps/studio/components/interfaces/App/FeaturePreview/InlineEditorPreview.tsx b/apps/studio/components/interfaces/App/FeaturePreview/InlineEditorPreview.tsx index 71f85053224..7b9beaa3d26 100644 --- a/apps/studio/components/interfaces/App/FeaturePreview/InlineEditorPreview.tsx +++ b/apps/studio/components/interfaces/App/FeaturePreview/InlineEditorPreview.tsx @@ -10,7 +10,7 @@ export const InlineEditorPreview = () => { width={1296} height={900} alt="api-docs-side-panel-preview" - className="rounded border mb-4" + className="rounded-sm border mb-4" />

        Edit policies, functions, and triggers directly in the inline SQL editor. When you select diff --git a/apps/studio/components/interfaces/App/FeaturePreview/PgDeltaDiffPreview.tsx b/apps/studio/components/interfaces/App/FeaturePreview/PgDeltaDiffPreview.tsx index 98b0bacfc5f..677fd196501 100644 --- a/apps/studio/components/interfaces/App/FeaturePreview/PgDeltaDiffPreview.tsx +++ b/apps/studio/components/interfaces/App/FeaturePreview/PgDeltaDiffPreview.tsx @@ -21,7 +21,7 @@ export const PgDeltaDiffPreview = () => {

      -
      +

      Enabling this preview will:

      • Use pg-delta to compute schema diffs when comparing branches, instead of migra
      • diff --git a/apps/studio/components/interfaces/App/FeaturePreview/RLSTesterPreview.tsx b/apps/studio/components/interfaces/App/FeaturePreview/RLSTesterPreview.tsx index 704b0dfd8f1..4f8a00c3def 100644 --- a/apps/studio/components/interfaces/App/FeaturePreview/RLSTesterPreview.tsx +++ b/apps/studio/components/interfaces/App/FeaturePreview/RLSTesterPreview.tsx @@ -20,9 +20,9 @@ export const RLSTesterPreview = () => { height={900} quality={100} alt="rls-tester-preview" - className="rounded border" + className="rounded-sm border" /> -
        +

        Enabling this preview will:

        • diff --git a/apps/studio/components/interfaces/App/FeaturePreview/UnifiedLogsPreview.tsx b/apps/studio/components/interfaces/App/FeaturePreview/UnifiedLogsPreview.tsx index fc623381159..7448f63c2d8 100644 --- a/apps/studio/components/interfaces/App/FeaturePreview/UnifiedLogsPreview.tsx +++ b/apps/studio/components/interfaces/App/FeaturePreview/UnifiedLogsPreview.tsx @@ -22,10 +22,10 @@ export const UnifiedLogsPreview = () => { src={`${BASE_PATH}/img/previews/new-logs-preview.png`} width={1296} height={900} - className="rounded border mb-4" + className="rounded-sm border mb-4" /> -
          +

          Enabling this preview will:

          • diff --git a/apps/studio/components/interfaces/Auth/AuditLogsForm.tsx b/apps/studio/components/interfaces/Auth/AuditLogsForm.tsx index 4e2a60fb64c..5c187bd0d45 100644 --- a/apps/studio/components/interfaces/Auth/AuditLogsForm.tsx +++ b/apps/studio/components/interfaces/Auth/AuditLogsForm.tsx @@ -173,7 +173,7 @@ export const AuditLogsForm = () => { . You are responsible for backing up, copying, or migrating existing data from the{' '} - + {AUDIT_LOG_ENTRIES_TABLE} {' '} table if needed. diff --git a/apps/studio/components/interfaces/Auth/AuthProvidersForm/AuthProvidersForm.constants.ts b/apps/studio/components/interfaces/Auth/AuthProvidersForm/AuthProvidersForm.constants.ts index cb272c121c5..a4e1318751e 100644 --- a/apps/studio/components/interfaces/Auth/AuthProvidersForm/AuthProvidersForm.constants.ts +++ b/apps/studio/components/interfaces/Auth/AuthProvidersForm/AuthProvidersForm.constants.ts @@ -6,7 +6,7 @@ export const ProviderCollapsibleClasses = [ 'hover:border-strong data-open:border-strong', 'data-open:pb-px col-span-12 mx-auto', '-space-y-px overflow-hidden', - 'border shadow', + 'border shadow-sm', 'transition', 'first:rounded-tl', 'first:rounded-tr', diff --git a/apps/studio/components/interfaces/Auth/CustomAuthProviders/CreateOrUpdateCustomProviderSheet.tsx b/apps/studio/components/interfaces/Auth/CustomAuthProviders/CreateOrUpdateCustomProviderSheet.tsx index e7765b0dc50..8916086b469 100644 --- a/apps/studio/components/interfaces/Auth/CustomAuthProviders/CreateOrUpdateCustomProviderSheet.tsx +++ b/apps/studio/components/interfaces/Auth/CustomAuthProviders/CreateOrUpdateCustomProviderSheet.tsx @@ -261,7 +261,7 @@ export const CreateOrUpdateCustomProviderSheet = ({ { Enabled -
            +
            @@ -421,7 +421,7 @@ export const CustomAuthProvidersList = () => {
diff --git a/apps/studio/components/interfaces/Auth/EmailTemplates/TemplateEditor.tsx b/apps/studio/components/interfaces/Auth/EmailTemplates/TemplateEditor.tsx index b701cdb43e7..7bedec7cfd1 100644 --- a/apps/studio/components/interfaces/Auth/EmailTemplates/TemplateEditor.tsx +++ b/apps/studio/components/interfaces/Auth/EmailTemplates/TemplateEditor.tsx @@ -285,12 +285,12 @@ export const TemplateEditor = ({ template }: TemplateEditorProps) => {
{activeView === 'source' ? ( <> -
+
{ setBodyValue(e ?? '') if (bodyValue !== e) setHasUnsavedChanges(true) @@ -325,7 +325,7 @@ export const TemplateEditor = ({ template }: TemplateEditorProps) => { ) : ( <>