From 7bac134fa5d5e554b44f1656572ef2a3b20e59ae Mon Sep 17 00:00:00 2001 From: Eduardo Gurgel Date: Wed, 19 Aug 2026 19:30:22 +1200 Subject: [PATCH 1/9] fix(studio): use realtime entitlements for max settings (#49228) * Realtime settings now respect plan-based limits while enforcing safe maximum caps. * Validation messages dynamically reflect the applicable limit. * Settings validate correctly whether realtime access is active or suspended. * Saving remains disabled until all applicable realtime limits are loaded. * A loading indicator appears while settings and limits are retrieved. * Improved form reset behavior, accessibility labels, and settings guidance. --- .../interfaces/Realtime/RealtimeSettings.tsx | 238 ++++++++++++++---- 1 file changed, 183 insertions(+), 55 deletions(-) diff --git a/apps/studio/components/interfaces/Realtime/RealtimeSettings.tsx b/apps/studio/components/interfaces/Realtime/RealtimeSettings.tsx index 52e2a3adb1330..13963a9f4a1f0 100644 --- a/apps/studio/components/interfaces/Realtime/RealtimeSettings.tsx +++ b/apps/studio/components/interfaces/Realtime/RealtimeSettings.tsx @@ -23,6 +23,7 @@ import { import { Admonition } from 'ui-patterns/Admonition' import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal' import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' +import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader' import * as z from 'zod' import { AlertError } from '@/components/ui/AlertError' @@ -35,12 +36,20 @@ import { REALTIME_DEFAULT_CONFIG, useRealtimeConfigurationQuery, } from '@/data/realtime/realtime-config-query' +import { useCheckEntitlements } from '@/hooks/misc/useCheckEntitlements' import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization' import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' const formId = 'realtime-configuration-form' +const REALTIME_SOFT_LIMITS = { + max_concurrent_users: 50_000, + max_events_per_second: 50_000, + max_presence_events_per_second: 5_000, + max_payload_size_in_kb: 3_000, +} + export const RealtimeSettings = () => { const { ref: projectRef } = useParams() const { data: project } = useSelectedProjectQuery() @@ -56,7 +65,7 @@ export const RealtimeSettings = () => { projectRef: project?.ref, connectionString: project?.connectionString, }) - const { data, error, isError } = useRealtimeConfigurationQuery({ + const { data, error, isError, isPending } = useRealtimeConfigurationQuery({ projectRef, }) @@ -66,6 +75,45 @@ export const RealtimeSettings = () => { schemas: ['realtime'], }) + // Per-plan realtime ceilings come from the org's entitlements (plan + any overrides). + // The effective limit is the lower of the entitlement and the soft cap, so an unlimited + // plan is still bounded and self-hosted / loading falls back to the soft cap. + const { getEntitlementMax: getMaxConcurrentUsers, isSuccess: isSuccessMaxConcurrentUsers } = + useCheckEntitlements('realtime.max_concurrent_users') + const { getEntitlementMax: getMaxEventsPerSecond, isSuccess: isSuccessMaxEventsPerSecond } = + useCheckEntitlements('realtime.max_events_per_second') + const { + getEntitlementMax: getMaxPresenceEventsPerSecond, + isSuccess: isSuccessMaxPresenceEventsPerSecond, + } = useCheckEntitlements('realtime.max_presence_events_per_second') + const { getEntitlementMax: getMaxPayloadSizeInKb, isSuccess: isSuccessMaxPayloadSizeInKb } = + useCheckEntitlements('realtime.max_payload_size_in_kb') + + const isRealtimeEntitlementsLoaded = + isSuccessMaxConcurrentUsers && + isSuccessMaxEventsPerSecond && + isSuccessMaxPresenceEventsPerSecond && + isSuccessMaxPayloadSizeInKb + + const isLoading = isPending || !isRealtimeEntitlementsLoaded + + const maxConcurrentUsersLimit = Math.min( + getMaxConcurrentUsers() ?? Infinity, + REALTIME_SOFT_LIMITS.max_concurrent_users + ) + const maxEventsPerSecondLimit = Math.min( + getMaxEventsPerSecond() ?? Infinity, + REALTIME_SOFT_LIMITS.max_events_per_second + ) + const maxPresenceEventsPerSecondLimit = Math.min( + getMaxPresenceEventsPerSecond() ?? Infinity, + REALTIME_SOFT_LIMITS.max_presence_events_per_second + ) + const maxPayloadSizeInKbLimit = Math.min( + getMaxPayloadSizeInKb() ?? Infinity, + REALTIME_SOFT_LIMITS.max_payload_size_in_kb + ) + const isFreePlan = organization?.plan.id === 'free' const isUsageBillingEnabled = organization?.usage_billing_enabled const isRealtimeDisabled = data?.suspend ?? REALTIME_DEFAULT_CONFIG.suspend @@ -93,10 +141,38 @@ export const RealtimeSettings = () => { .min(1) .max(maxConn?.maxConnections ?? 100) .optional(), - max_concurrent_users: z.coerce.number().min(1).max(50000).optional(), - max_events_per_second: z.coerce.number().min(1).max(50000).optional(), - max_presence_events_per_second: z.coerce.number().min(1).max(5000).optional(), - max_payload_size_in_kb: z.coerce.number().min(1).max(10000).optional(), + max_concurrent_users: z.coerce + .number() + .min(1) + .max( + maxConcurrentUsersLimit, + `Cannot exceed ${maxConcurrentUsersLimit.toLocaleString()} concurrent clients` + ) + .optional(), + max_events_per_second: z.coerce + .number() + .min(1) + .max( + maxEventsPerSecondLimit, + `Cannot exceed ${maxEventsPerSecondLimit.toLocaleString()} events per second` + ) + .optional(), + max_presence_events_per_second: z.coerce + .number() + .min(1) + .max( + maxPresenceEventsPerSecondLimit, + `Cannot exceed ${maxPresenceEventsPerSecondLimit.toLocaleString()} presence events per second` + ) + .optional(), + max_payload_size_in_kb: z.coerce + .number() + .min(1) + .max( + maxPayloadSizeInKbLimit, + `Cannot exceed ${maxPayloadSizeInKbLimit.toLocaleString()} KB` + ) + .optional(), // [Joshen] These fields are temporarily hidden from the UI // max_bytes_per_second: z.coerce.number().min(1).max(10000000).optional(), // max_channels_per_client: z.coerce.number().min(1).max(10000).optional(), @@ -110,10 +186,34 @@ export const RealtimeSettings = () => { .number() .min(1) .max(maxConn?.maxConnections ?? 100), - max_concurrent_users: z.coerce.number().min(1).max(50000), - max_events_per_second: z.coerce.number().min(1).max(50000), - max_presence_events_per_second: z.coerce.number().min(1).max(5000), - max_payload_size_in_kb: z.coerce.number().min(1).max(10000), + max_concurrent_users: z.coerce + .number() + .min(1) + .max( + maxConcurrentUsersLimit, + `Cannot exceed ${maxConcurrentUsersLimit.toLocaleString()} concurrent clients` + ), + max_events_per_second: z.coerce + .number() + .min(1) + .max( + maxEventsPerSecondLimit, + `Cannot exceed ${maxEventsPerSecondLimit.toLocaleString()} events per second` + ), + max_presence_events_per_second: z.coerce + .number() + .min(1) + .max( + maxPresenceEventsPerSecondLimit, + `Cannot exceed ${maxPresenceEventsPerSecondLimit.toLocaleString()} presence events per second` + ), + max_payload_size_in_kb: z.coerce + .number() + .min(1) + .max( + maxPayloadSizeInKbLimit, + `Cannot exceed ${maxPayloadSizeInKbLimit.toLocaleString()} KB` + ), // [Joshen] These fields are temporarily hidden from the UI // max_bytes_per_second: z.coerce.number().min(1).max(10000000), // max_channels_per_client: z.coerce.number().min(1).max(10000), @@ -123,16 +223,32 @@ export const RealtimeSettings = () => { }), ]) + const configValues = data ?? REALTIME_DEFAULT_CONFIG + const sharedFormValues = { + connection_pool: configValues.connection_pool ?? REALTIME_DEFAULT_CONFIG.connection_pool, + max_concurrent_users: + configValues.max_concurrent_users ?? REALTIME_DEFAULT_CONFIG.max_concurrent_users, + max_events_per_second: + configValues.max_events_per_second ?? REALTIME_DEFAULT_CONFIG.max_events_per_second, + max_presence_events_per_second: + configValues.max_presence_events_per_second ?? + REALTIME_DEFAULT_CONFIG.max_presence_events_per_second, + max_payload_size_in_kb: + configValues.max_payload_size_in_kb ?? REALTIME_DEFAULT_CONFIG.max_payload_size_in_kb, + allow_public: !(configValues.private_only ?? REALTIME_DEFAULT_CONFIG.private_only), + } + const formValues: z.infer = { + suspend: configValues.suspend ?? REALTIME_DEFAULT_CONFIG.suspend, + ...sharedFormValues, + } + const form = useForm>({ resolver: zodResolver(FormSchema), defaultValues: { ...REALTIME_DEFAULT_CONFIG, allow_public: !REALTIME_DEFAULT_CONFIG.private_only, }, - values: { - ...(data ?? REALTIME_DEFAULT_CONFIG), - allow_public: !(data?.private_only ?? REALTIME_DEFAULT_CONFIG.private_only), - } as any, + values: formValues, }) const [allow_public, suspend] = useWatch({ @@ -145,11 +261,13 @@ export const RealtimeSettings = () => { const onSubmit: SubmitHandler> = (_data) => { if (!projectRef) return console.error('Project ref is required') + if (!isRealtimeEntitlementsLoaded) return setIsConfirmNextModalOpen(true) } const onConfirmSave = () => { if (!projectRef) return console.error('Project ref is required') + if (!isRealtimeEntitlementsLoaded) return const values = form.getValues() // [Joshen] Casting to `Number` here as the values are being set as string when edited in the form @@ -184,6 +302,16 @@ export const RealtimeSettings = () => { }) } + if (isLoading) { + return ( + + + + + + ) + } + return ( <>
@@ -207,6 +335,7 @@ export const RealtimeSettings = () => { field.onChange(!checked)} disabled={!canUpdateConfig} @@ -221,30 +350,25 @@ export const RealtimeSettings = () => { -
-
-
- {isDisablingRealtime - ? 'Realtime service will be disabled' - : isEnablingRealtime - ? 'Realtime service will be re-enabled' - : isRealtimeDisabled - ? 'Realtime service is disabled' - : null} -
-

- {isDisablingRealtime - ? 'Clients will no longer be able to connect to your project’s realtime service once saved' - : isEnablingRealtime - ? "Clients will be able to connect to your project's realtime service again once saved" - : isRealtimeDisabled - ? 'You will need to enable it to continue using Realtime' - : null} -

-
-
-
+ title={ + isDisablingRealtime + ? 'Realtime service will be disabled' + : isEnablingRealtime + ? 'Realtime service will be re-enabled' + : isRealtimeDisabled + ? 'Realtime service is disabled' + : '' + } + description={ + isDisablingRealtime + ? 'Clients will no longer be able to connect to your project’s realtime service once saved' + : isEnablingRealtime + ? "Clients will be able to connect to your project's realtime service again once saved" + : isRealtimeDisabled + ? 'You will need to enable it to continue using Realtime' + : null + } + /> )} @@ -265,6 +389,7 @@ export const RealtimeSettings = () => { { !isRealtimeDisabled && ( -

- Private mode is {isSettingToPrivate ? 'being ' : ''} - enabled, but no RLS policies exists on the{' '} - - realtime.messages - {' '} - table. No messages will be received by users. -

- - - +

+ Private mode is {isSettingToPrivate ? 'being ' : ''} + enabled, but no RLS policies exist on the{' '} + realtime.messages{' '} + table. No messages will be received by users. +

+ } + actions={ + } /> )} @@ -555,7 +678,7 @@ export const RealtimeSettings = () => {
{form.formState.isDirty && ( - )} @@ -563,7 +686,12 @@ export const RealtimeSettings = () => { variant="primary" type="submit" form={formId} - disabled={!canUpdateConfig || isUpdatingConfig || !form.formState.isDirty} + disabled={ + !canUpdateConfig || + isUpdatingConfig || + !form.formState.isDirty || + !isRealtimeEntitlementsLoaded + } loading={isUpdatingConfig} > Save changes From b3edaa02f0464c49b290ffc2afeff8982291e741 Mon Sep 17 00:00:00 2001 From: Danny White <3104761+dnywh@users.noreply.github.com> Date: Wed, 19 Aug 2026 17:37:14 +1000 Subject: [PATCH 2/9] feat(studio): remove unified logs banner and deprioritise tos banner (#49239) ## What kind of change does this PR introduce? Studio UI cleanup for sidebar BannerStack items and Unified Logs preview defaults. ## What is the current behavior? The sidebar BannerStack shows both a Unified Logs promo banner and a Terms of Service update notice. Unified Logs has been default opt-in for a while, and the ToS banner currently shares priority with other higher-value notices. The default opt-in behaviour is still gated behind the `unifiedLogsDefaultOptIn` feature flag. Closes [DEPR-646](https://linear.app/supabase/issue/DEPR-646/remove-unified-logs-banner-and-deprioritise-tos-banner). | Before | | --- | | 5717 | ## What is the new behavior? - Removes the Unified Logs BannerStack item and its component - Keeps the ToS update banner but lowers its priority so other banners surface first - Sets Unified Logs `isDefaultOptIn` to `true` and removes `unifiedLogsDefaultOptIn` flag usage ## To test - Open any project in Studio (e.g. `/project/`) - Confirm the sidebar BannerStack no longer shows the "Unified Logs is here" banner - If you have not dismissed the ToS notice and it is still before the expiry date, confirm it still appears but sits behind higher-priority banners (e.g. free micro upgrade on eligible projects) - Open `/project//logs` and confirm Unified Logs loads by default for users who have not previously toggled the preview off ## After merge - [ ] Retire the `unifiedLogsDefaultOptIn` PostHog flag ## Summary by CodeRabbit - **New Features** - Unified Logs preview is now enabled by default when available, while preserving individual user choices. - **Bug Fixes** - Terms of Service update notifications now appear with higher priority. - **Changes** - Removed the Unified Logs promotional banner, including related navigation, dismissal, and tracking behavior. --- .../interfaces/App/AppBannerWrapper.tsx | 2 +- .../FeaturePreview/FeaturePreviewContext.tsx | 4 +- .../App/FeaturePreview/useFeaturePreviews.ts | 4 +- .../layouts/ProjectLayout/index.test.tsx | 15 -- .../layouts/ProjectLayout/index.tsx | 21 +-- .../ui/BannerStack/BannerStackProvider.tsx | 1 - .../BannerStack/Banners/BannerUnifiedLogs.tsx | 173 ------------------ 7 files changed, 4 insertions(+), 216 deletions(-) delete mode 100644 apps/studio/components/ui/BannerStack/Banners/BannerUnifiedLogs.tsx diff --git a/apps/studio/components/interfaces/App/AppBannerWrapper.tsx b/apps/studio/components/interfaces/App/AppBannerWrapper.tsx index 832f2769a756d..dbb1d6cef9601 100644 --- a/apps/studio/components/interfaces/App/AppBannerWrapper.tsx +++ b/apps/studio/components/interfaces/App/AppBannerWrapper.tsx @@ -30,7 +30,7 @@ export const AppBannerWrapper = ({ children }: PropsWithChildren<{}>) => { id: 'tos-update-banner', isDismissed: false, content: , - priority: 2, + priority: 0, }) } else { dismissBanner('tos-update-banner') diff --git a/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreviewContext.tsx b/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreviewContext.tsx index 03691172a714d..2eeb77a318fc3 100644 --- a/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreviewContext.tsx +++ b/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreviewContext.tsx @@ -98,15 +98,13 @@ export const useIsColumnLevelPrivilegesEnabled = () => { } export const useUnifiedLogsPreview = () => { - const unifiedLogsDefaultOptIn = useFlag('unifiedLogsDefaultOptIn') const { flags, isInitialized, onUpdateFlag } = useFeaturePreviewContext() const isLoading = !isInitialized const isEnabled = IS_PLATFORM && flags[LOCAL_STORAGE_KEYS.UI_PREVIEW_UNIFIED_LOGS] const hasToggledPreview = !!safeLocalStorage.getItem(LOCAL_STORAGE_KEYS.UI_PREVIEW_UNIFIED_LOGS) - const isDefaultOptIn = - IS_PLATFORM && isInitialized && unifiedLogsDefaultOptIn && !hasToggledPreview + const isDefaultOptIn = IS_PLATFORM && !hasToggledPreview const enable = () => onUpdateFlag(LOCAL_STORAGE_KEYS.UI_PREVIEW_UNIFIED_LOGS, true) const disable = () => onUpdateFlag(LOCAL_STORAGE_KEYS.UI_PREVIEW_UNIFIED_LOGS, false) diff --git a/apps/studio/components/interfaces/App/FeaturePreview/useFeaturePreviews.ts b/apps/studio/components/interfaces/App/FeaturePreview/useFeaturePreviews.ts index a30be45c94144..607337c5384a3 100644 --- a/apps/studio/components/interfaces/App/FeaturePreview/useFeaturePreviews.ts +++ b/apps/studio/components/interfaces/App/FeaturePreview/useFeaturePreviews.ts @@ -36,7 +36,6 @@ export const useFeaturePreviews = (): FeaturePreview[] => { const isMarketplaceEnabled = useFlag('marketplaceIntegrations') const isDatabaseConnectionsEnabled = useFlag('topForPostgres') - const unifiedLogsDefaultOptIn = useFlag('unifiedLogsDefaultOptIn') const isSqlEditorManualSaveForced = useFlag('sqlEditorManualSaveForced') return useMemo(() => { @@ -49,7 +48,7 @@ export const useFeaturePreviews = (): FeaturePreview[] => { enabled: true, isNew: true, isPlatformOnly: true, - isDefaultOptIn: unifiedLogsDefaultOptIn, + isDefaultOptIn: true, getRoute: (ref?: string) => `/project/${ref}/logs`, }, { @@ -142,7 +141,6 @@ export const useFeaturePreviews = (): FeaturePreview[] => { return previews.sort((a, b) => Number(b.isNew) - Number(a.isNew)) }, [ - unifiedLogsDefaultOptIn, isSqlEditorManualSaveForced, isPlatformWebhooksEnabled, jitDbAccessEnabled, diff --git a/apps/studio/components/layouts/ProjectLayout/index.test.tsx b/apps/studio/components/layouts/ProjectLayout/index.test.tsx index d7bd6257232f2..e02dcd5f7ddaa 100644 --- a/apps/studio/components/layouts/ProjectLayout/index.test.tsx +++ b/apps/studio/components/layouts/ProjectLayout/index.test.tsx @@ -79,7 +79,6 @@ vi.mock('common', () => ({ `free-micro-upgrade-banner-dismissed-${ref}`, PROJECT_INTEGRATION_BANNER_DISMISSED: (ref: string, integrationSource: string) => `project-integration-banner-dismissed-${ref}-${integrationSource}`, - UNIFIED_LOGS_BANNER_DISMISSED: 'unified-logs-banner-dismissed', }, isFeatureEnabled: () => false, })) @@ -177,7 +176,6 @@ vi.mock('@/hooks/misc/useLocalStorage', () => ({ vi.mock('@/components/ui/BannerStack/BannerStackProvider', () => ({ BANNER_ID: { FREE_MICRO_UPGRADE: 'free-micro-upgrade-banner', - UNIFIED_LOGS: 'unified-logs-banner', }, useBannerStack: () => ({ addBanner: mockAddBanner, @@ -190,19 +188,6 @@ vi.mock('@/components/ui/BannerStack/Banners/BannerFreeMicroUpgrade', () => ({ BannerFreeMicroUpgrade: () => null, })) -vi.mock('@/components/ui/BannerStack/Banners/BannerUnifiedLogs', () => ({ - BannerUnifiedLogs: () => null, -})) - -vi.mock('@/components/interfaces/App/FeaturePreview/FeaturePreviewContext', () => ({ - useUnifiedLogsPreview: () => ({ - isEnabled: false, - isLoading: false, - enable: () => {}, - disable: () => {}, - }), -})) - vi.mock('@/data/usage/resource-warnings-query', () => ({ useResourceWarningsQuery: () => ({ data: mockResourceWarningsState.current }), })) diff --git a/apps/studio/components/layouts/ProjectLayout/index.tsx b/apps/studio/components/layouts/ProjectLayout/index.tsx index d9d487962d34f..85a04b89b605a 100644 --- a/apps/studio/components/layouts/ProjectLayout/index.tsx +++ b/apps/studio/components/layouts/ProjectLayout/index.tsx @@ -1,4 +1,4 @@ -import { IS_PLATFORM, LOCAL_STORAGE_KEYS, mergeRefs, useParams } from 'common' +import { LOCAL_STORAGE_KEYS, mergeRefs, useParams } from 'common' import { AnimatePresence, motion } from 'framer-motion' import { XIcon } from 'lucide-react' import Head from 'next/head' @@ -43,7 +43,6 @@ import { UpgradingState } from './UpgradingState' import { CreateBranchModal } from '@/components/interfaces/BranchManagement/CreateBranchModal' import { ProjectAPIDocs } from '@/components/interfaces/ProjectAPIDocs/ProjectAPIDocs' import { BannerFreeMicroUpgrade } from '@/components/ui/BannerStack/Banners/BannerFreeMicroUpgrade' -import { BannerUnifiedLogs } from '@/components/ui/BannerStack/Banners/BannerUnifiedLogs' import { BANNER_ID, useBannerStack } from '@/components/ui/BannerStack/BannerStackProvider' import { ButtonTooltip } from '@/components/ui/ButtonTooltip' import PartnerIcon from '@/components/ui/PartnerIcon' @@ -155,10 +154,6 @@ export const ProjectLayout = forwardRef { - if (!selectedProject?.ref) return - if (IS_PLATFORM && !isUnifiedLogsBannerDismissed) { - addBanner({ - id: BANNER_ID.UNIFIED_LOGS, - isDismissed: false, - content: , - priority: 1, - }) - } else { - dismissBanner(BANNER_ID.UNIFIED_LOGS) - } - }, [selectedProject?.ref, isUnifiedLogsBannerDismissed, addBanner, dismissBanner]) - useLayoutEffect(() => { const unregister = registerOpenMenu(() => { setMobileSheetContent( diff --git a/apps/studio/components/ui/BannerStack/BannerStackProvider.tsx b/apps/studio/components/ui/BannerStack/BannerStackProvider.tsx index 5049cd000e0aa..60615a560789a 100644 --- a/apps/studio/components/ui/BannerStack/BannerStackProvider.tsx +++ b/apps/studio/components/ui/BannerStack/BannerStackProvider.tsx @@ -7,7 +7,6 @@ export const BANNER_ID = { RLS_EVENT_TRIGGER: 'rls-event-trigger-banner', FREE_MICRO_UPGRADE: 'free-micro-upgrade-banner', TOS_UPDATE: 'tos-update-banner', - UNIFIED_LOGS: 'unified-logs-banner', } as const export type BannerId = (typeof BANNER_ID)[keyof typeof BANNER_ID] diff --git a/apps/studio/components/ui/BannerStack/Banners/BannerUnifiedLogs.tsx b/apps/studio/components/ui/BannerStack/Banners/BannerUnifiedLogs.tsx deleted file mode 100644 index 9d417dfcebc1e..0000000000000 --- a/apps/studio/components/ui/BannerStack/Banners/BannerUnifiedLogs.tsx +++ /dev/null @@ -1,173 +0,0 @@ -import { LOCAL_STORAGE_KEYS } from 'common' -import { useParams } from 'common/hooks' -import dayjs from 'dayjs' -import { AnimatePresence, motion } from 'framer-motion' -import Link from 'next/link' -import { useEffect, useRef, useState } from 'react' -import { Badge, Button, cn } from 'ui' - -import { BannerCard } from '../BannerCard' -import { useBannerStack } from '../BannerStackProvider' -import { - useFeaturePreviewModal, - useUnifiedLogsPreview, -} from '@/components/interfaces/App/FeaturePreview/FeaturePreviewContext' -import { useLocalStorageQuery } from '@/hooks/misc/useLocalStorage' -import { useTrack } from '@/lib/telemetry/track' - -// Number of rows visible in the carousel viewport. We keep one extra row in -// state so the row sliding out of view has something to animate towards. -const VISIBLE_ROWS = 3 -const ROW_HEIGHT = 28 -const TICK_MS = 3000 - -const SAMPLES = [200, 200, 201, 200, 304, 400, 200, 500] as const - -interface LogEntry { - id: number - timestamp: string - status: number -} - -const makeEntry = (id: number, offsetSeconds = 0): LogEntry => ({ - id, - timestamp: dayjs().subtract(offsetSeconds, 'second').format('DD MMM YY HH:mm:ss'), - status: SAMPLES[id % SAMPLES.length], -}) - -const LogRow = ({ entry }: { entry: LogEntry }) => { - const isDestructive = entry.status >= 500 - const isWarning = entry.status >= 400 && entry.status < 500 - return ( -
- - - {entry.timestamp} - - - {entry.status} - -
- ) -} - -const UnifiedLogsCarousel = () => { - const counter = useRef(VISIBLE_ROWS) - const [logs, setLogs] = useState(() => - Array.from({ length: VISIBLE_ROWS + 1 }, (_, i) => makeEntry(VISIBLE_ROWS - i, i)) - ) - - useEffect(() => { - const interval = setInterval(() => { - counter.current += 1 - const next = makeEntry(counter.current) - setLogs((prev) => [next, ...prev].slice(0, VISIBLE_ROWS + 1)) - }, TICK_MS) - return () => clearInterval(interval) - }, []) - - return ( -
- - {logs.map((entry, index) => ( - - - - ))} - -
- ) -} - -export const BannerUnifiedLogs = () => { - const { ref } = useParams() - const track = useTrack() - const { dismissBanner } = useBannerStack() - const { isEnabled } = useUnifiedLogsPreview() - const { selectFeaturePreview } = useFeaturePreviewModal() - const [, setIsDismissed] = useLocalStorageQuery( - LOCAL_STORAGE_KEYS.UNIFIED_LOGS_BANNER_DISMISSED, - false - ) - - return ( - { - setIsDismissed(true) - dismissBanner('unified-logs-banner') - track('unified_logs_banner_dismiss_button_clicked') - }} - > -
-
- - Beta - -
- -
-
-
-

Unified Logs is here

-

- Search and correlate logs across all of your services from a single place. -

-
-
- {isEnabled ? ( - - ) : ( - - )} -
-
-
- ) -} From 4e280d4498e55ec5bf6cf133ce979602b66baa23 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <209825114+claude[bot]@users.noreply.github.com> Date: Wed, 19 Aug 2026 15:39:07 +0800 Subject: [PATCH 3/9] fix(studio): disambiguate query cancel telemetry and gate live-mode hotkey (#49137) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit _Requested by **Pam Chia** · [Slack thread](https://supabase.slack.com/archives/C076KTY11DF/p1786930264662829?thread_ts=1786930264.662829&cid=C076KTY11DF)_ ## 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? Bug fix. Two telemetry correctness fixes in the Database Connections feature preview. No visual changes, no new events. Linear: [GROWTH-1107](https://linear.app/supabase/issue/GROWTH-1107/fix-database-connections-feature-preview-banner-dead-end-plus) ## What is the current behavior? ### 1. `query_cancel_button_clicked` cannot tell its two surfaces apart "Cancel query" is reachable from two places on `/observability/connections`. One is the three-dot dropdown menu on an activity row. The other is inside the "Confirm to terminate this session?" dialog, which offers "Cancel query" alongside "Terminate" when the session is running a query. **Before:** both buttons fire `query_cancel_button_clicked` with an identical payload (`activityState`, `isBlocking`). In analysis the two are one undifferentiated number, so there is no way to see whether people cancel straight from the row or only after opening the terminate dialog and reading the "Cancelling it may solve the problem without closing the connection" warning. That warning is the main nudge away from terminating, and today we cannot measure whether it lands. ### 2. The live-mode hotkey fires telemetry for users who do not have the feature **Before:** the Mod+J live-mode shortcut is registered whenever the page mounts, regardless of whether the Database Connections feature preview is enabled. The live badge, the toggle button and the activity query are all gated on the feature, so a user without it can press Mod+J, emit `database_connections_live_mode_clicked`, and see nothing change. Those events inflate the metric with interactions that had no effect. ## What is the new behavior? ### 1. `query_cancel_button_clicked` carries an `origin` **After:** the event reports which surface it came from, so the two flows can be split in analysis. Nothing changes for the user. `QueryCancelButtonClickedEvent` in `packages/common/telemetry-constants.ts` gains a required `origin: 'dropdown_menu' | 'terminate_dialog'` property, following the shape already used by `index_advisor_enable_button_clicked` (`origin: 'banner' | 'dialog'`). Values are snake_case to match the dominant convention among the existing `origin` unions in that file. In `ActivityRow.tsx` the shared `onCancelQuery` handler now takes the origin as an argument and each of the two call sites passes its own value. Because `track()` is strictly typed per action, the required property is enforced at compile time rather than by convention. ### 2. The live-mode hotkey is gated on the feature **After:** Mod+J only does something, and only reports something, for users who actually have Database Connections enabled. Everyone else is unaffected, as before. `useShortcut` already accepts an `enabled` option that disables the hotkey and hides the command-menu entry. The registration in `pages/project/[ref]/observability/connections.tsx` now passes `enabled: isDatabaseConnectionsEnabled`, reusing the value already read from `useIsDatabaseConnectionsEnabled()` and already used to gate the activity query and the visible controls on the same page. ## Additional context **Scope was reduced from the original plan.** GROWTH-1107 originally covered four items. #49132 rewrote the Database Connections gating model and superseded three of them, so only the two above remain: - The feature preview banner is no longer flag-gated, so there is nothing to gate on `topForPostgres`. - `isEnabled` on `database_connections_banner_cta_button_clicked` is now a real variable rather than a constant, since it is true on the new "Explore Database Connections" variant. It stays as is. - The wrong-feature fallback in the feature preview modal no longer triggers for this preview. Nothing in that area is touched here. GROWTH-1107 has been updated to reflect the reduced scope. **Validation** (run locally): - `tsc --noEmit` in `packages/common` and in `apps/studio`. Studio reports the same two pre-existing errors before and after this change and none in the changed files. - `eslint` on both changed studio files: clean. `lint:ratchet`: passes. - `vitest --run components/interfaces/Observability/DatabaseConnections`: 36 passed. - Prettier check on all three files: clean. ## To test Verified in a real browser on the studio-staging Vercel preview, checking telemetry at the wire level (network inspection of `POST /platform/telemetry/event`). Checks derived from the diff, covering both fixes and their negative cases. - [x] Mod+J with the Database Connections feature preview off: no `database_connections_live_mode_clicked` request fired and no UI change; the page stays on the enable-preview gate screen - [x] Mod+J with the preview on: the live badge visibly toggles and exactly one event fires per press (`newState: "disabled"` on the first press since live mode starts on by default, then `"enabled"` on the second) - [x] "Cancel query" from the activity row dropdown on an active `pg_sleep(120)` session: `query_cancel_button_clicked` with `custom_properties: {"activityState":"active","isBlocking":false,"origin":"dropdown_menu"}` - [x] "Cancel query" inside the "Confirm to terminate this session?" dialog: `query_cancel_button_clicked` with `custom_properties: {"activityState":"active","isBlocking":false,"origin":"terminate_dialog"}` Opening the terminate dialog in the last check also fired `session_terminate_button_clicked`, correctly distinct from the cancel event. No new console errors versus the page-load baseline across all four checks. Co-authored-by: Claude --- .../Observability/DatabaseConnections/ActivityRow.tsx | 10 +++++++--- .../pages/project/[ref]/observability/connections.tsx | 1 + packages/common/telemetry-constants.ts | 7 ++++++- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/apps/studio/components/interfaces/Observability/DatabaseConnections/ActivityRow.tsx b/apps/studio/components/interfaces/Observability/DatabaseConnections/ActivityRow.tsx index 8f40dd2a16545..829290f74a2ef 100644 --- a/apps/studio/components/interfaces/Observability/DatabaseConnections/ActivityRow.tsx +++ b/apps/studio/components/interfaces/Observability/DatabaseConnections/ActivityRow.tsx @@ -149,11 +149,12 @@ export const ActivityRow = ({ activity.state === 'idle in transaction (aborted)') && durationSeconds >= WARN_DURATION_IDLE_TXN)) - const onCancelQuery = async () => { + const onCancelQuery = async (origin: 'dropdown_menu' | 'terminate_dialog') => { const isBlocking = (data ?? []).some((x) => x.blocked_by.includes(activity.pid)) track('query_cancel_button_clicked', { activityState: activity.state, isBlocking, + origin, }) const toastId = toast.loading(`Cancelling query (ID: ${activity.pid})`) @@ -445,7 +446,7 @@ export const ActivityRow = ({ disabled={ activity.state !== 'active' || superuserRoles?.includes(activity.role_name) } - onClick={onCancelQuery} + onClick={() => onCancelQuery('dropdown_menu')} tooltip={{ content: { side: 'left', @@ -503,7 +504,10 @@ export const ActivityRow = ({ Back
{activity.state === 'active' && ( - + onCancelQuery('terminate_dialog')} + > Cancel query )} diff --git a/apps/studio/pages/project/[ref]/observability/connections.tsx b/apps/studio/pages/project/[ref]/observability/connections.tsx index 10cc40a4cafe4..ad85042cac95f 100644 --- a/apps/studio/pages/project/[ref]/observability/connections.tsx +++ b/apps/studio/pages/project/[ref]/observability/connections.tsx @@ -40,6 +40,7 @@ export const DatabaseConnections: NextPageWithLayout = () => { const [now, setNow] = useState(() => dayjs.utc()) useShortcut(SHORTCUT_IDS.DATA_TABLE_TOGGLE_LIVE, handleToggleLive, { + enabled: isDatabaseConnectionsEnabled, registerInCommandMenu: false, }) diff --git a/packages/common/telemetry-constants.ts b/packages/common/telemetry-constants.ts index 7e97c3d26ef36..9596095bcaa8d 100644 --- a/packages/common/telemetry-constants.ts +++ b/packages/common/telemetry-constants.ts @@ -1605,7 +1605,8 @@ export interface SessionTerminateSubmittedEvent { } /** - * User clicked the Cancel query menu item for a database session in the Database Connections activity table. + * User clicked Cancel query for a database session in the Database Connections activity table, + * either from the row's dropdown menu or from the terminate session confirmation dialog. * * @group Events * @source studio @@ -1619,6 +1620,10 @@ export interface QueryCancelButtonClickedEvent { * Whether the session whose query is being cancelled was itself blocking one or more other sessions. */ isBlocking: boolean + /** + * Which surface the cancel was triggered from. + */ + origin: 'dropdown_menu' | 'terminate_dialog' } groups: TelemetryGroups } From 5d851f12ec42e7e0f1178cc6f60fefca57f24804 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Wed, 19 Aug 2026 15:49:15 +0800 Subject: [PATCH 4/9] Properly hook up browser tab label for explorer (#49240) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Context Very tiny one - just hooks up the browser tab label for explorer properly Browser tab should be the focused explorer tab, otherwise defaults to 'Explorer' image ## Summary by CodeRabbit - **Bug Fixes** - Browser titles now accurately reflect the active Explorer tab. - Untitled tabs display “Untitled,” while views without an active Explorer tab display “Explorer.” --- .../components/layouts/ExplorerLayout/ExplorerLayout.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/studio/components/layouts/ExplorerLayout/ExplorerLayout.tsx b/apps/studio/components/layouts/ExplorerLayout/ExplorerLayout.tsx index ce1fc5158f33d..75ed7b5d2bb21 100644 --- a/apps/studio/components/layouts/ExplorerLayout/ExplorerLayout.tsx +++ b/apps/studio/components/layouts/ExplorerLayout/ExplorerLayout.tsx @@ -37,9 +37,12 @@ export interface ExplorerLayoutProps extends ComponentProps { const [section, setSection] = useState() + const tabs = useTabsStateSnapshot() - // [Joshen] Temporary, to hook up with tabs store - const activeTabLabel = 'Active Tab Label' + const activeTab = tabs.activeTab ? tabs.tabsMap[tabs.activeTab] : undefined + const isActiveExplorerTab = + activeTab !== undefined && editorEntityTypes.explorer.includes(activeTab.type) + const activeTabLabel = isActiveExplorerTab ? activeTab.label || 'Untitled' : 'Explorer' const mergedBrowserTitle = { ...browserTitle, From 2034a1b929fbee2d0010531ff17c96a3cd03fd3c Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Wed, 19 Aug 2026 16:20:03 +0800 Subject: [PATCH 5/9] Use DiffEditor for QueryCell for logs migration (#49238) ## Context Previously we added the clickhouse logs migration banner for the Query Cell in Notebooks But rewriting was doing a direct swap of the content Changes here opt to use the DiffEditor instead to maintain the same UX for query editing that's not done by the user directly image ## Summary by CodeRabbit * **New Features** * Added a review workflow for legacy SQL rewrites. * View proposed rewrites in a full-editor comparison overlay. * Accept rewrites to update and save the SQL, or discard them without applying changes. * **Bug Fixes** * Prevented query execution, source changes, and visibility toggling while a rewrite is under review. * Prevented outdated rewrite proposals from overwriting newer SQL edits. --- .../Explorer/QueryEditor/QuerySourceMenu.tsx | 3 + .../interfaces/Explorer/QueryEditor/index.tsx | 59 ++++++++++++++++--- 2 files changed, 54 insertions(+), 8 deletions(-) diff --git a/apps/studio/components/interfaces/Explorer/QueryEditor/QuerySourceMenu.tsx b/apps/studio/components/interfaces/Explorer/QueryEditor/QuerySourceMenu.tsx index 450df7cec6f2c..61f6ffb8b6adc 100644 --- a/apps/studio/components/interfaces/Explorer/QueryEditor/QuerySourceMenu.tsx +++ b/apps/studio/components/interfaces/Explorer/QueryEditor/QuerySourceMenu.tsx @@ -26,6 +26,7 @@ import { import { type RoleImpersonationController } from '@/state/role-impersonation-state' export type QuerySourceMenuProps = { + disabled?: boolean rowLimit?: number onRowLimitChange?: (val: number) => void roleImpersonationState?: RoleImpersonationController @@ -43,6 +44,7 @@ export type QuerySourceMenuProps = { * has SQL to preserve or discard and a fresh draft does not. */ export const QuerySourceMenu = ({ + disabled = false, rowLimit = 100, onRowLimitChange, roleImpersonationState, @@ -74,6 +76,7 @@ export const QuerySourceMenu = ({ + +
+
+
+ +
+ + )} )} From e66d8eb0947973fdd8f26921a9ee3ca08474beb6 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <209825114+claude[bot]@users.noreply.github.com> Date: Wed, 19 Aug 2026 10:52:25 +0200 Subject: [PATCH 6/9] chore(deps): bump Supabase CLI to ^2.114.0 (speculative: Selfhosted Studio E2E `Start supabase` flake) (#49198) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit _Requested by **Ivan Vasilov** · [Slack thread](https://supabase.slack.com/archives/C063LNYJJKS/p1787058646458219?thread_ts=1787058646.458219&cid=C063LNYJJKS)_ **Before:** the root `package.json` pins the Supabase CLI at `supabase: ^2.76.10`, and `pnpm-lock.yaml` resolves it to `2.76.14`. **After:** it pins `supabase: ^2.114.0`. This bumps the Supabase CLI that `pnpm run e2e:setup:cli` and `pnpm run setup:cli` shell out to, so local dev and the E2E workflows boot the local stack with a CLI from this month instead of one from ~38 minor releases ago. **How:** a one-line version change to the `supabase` devDependency in the root `package.json`. Nothing else in the repo changes — no workflow, config, or test changes. ### ⚠️ This PR is incomplete: `pnpm-lock.yaml` still needs regenerating `pnpm-lock.yaml` is **not** updated in this PR, so `pnpm install --frozen-lockfile` will fail until someone runs: ```bash pnpm install --lockfile-only ``` and pushes the result to this branch. The lockfile could not be regenerated in the environment this PR was authored in: pnpm re-resolves `apps/studio`'s `"@std/path": "npm:@jsr/std__path@^1.0.8"` on every install, and `npm.jsr.io` is not reachable from there (`ERR_PNPM_FETCH_403`). Treat this PR as needing one extra commit before it can go green. ### Why `^2.114.0` and not `^2.115.0` `2.115.0` is the current `latest` on npm, but it was published only hours ago, and `pnpm-workspace.yaml` sets `minimumReleaseAge: 4320` (3 days) with `supabase` not in `minimumReleaseAgeExclude`. Pinning `2.115.0` today would fail the repo's own supply-chain check. `2.114.0` (2026-08-12) is the newest release that satisfies that policy. ## 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? Dependency bump. **Speculative** — this is an experiment, not a confirmed fix. ## What is the current behavior? The `Selfhosted Studio E2E Tests` workflow has been failing on `master` at the `Start supabase` step. Recent runs: - https://github.com/supabase/supabase/actions/runs/32092940311 - https://github.com/supabase/supabase/actions/runs/32131961447 In the Slack thread, Ivan Vasilov suggested trying a newer CLI and Alaister Young endorsed giving it a go. ## What is the new behavior? The workflow runs `supabase start` with CLI 2.114.0 instead of 2.76.14. The question this PR is trying to answer is simply **"does a newer CLI help this flake?"** It is not a diagnosis and not a claimed fix. If CI still fails at `Start supabase` on this branch, the bump can be kept or dropped on its own merits and the investigation continues elsewhere. ## Additional context **Verification status:** none locally. The bump was not exercised locally — this repo checkout has no `node_modules` (see the lockfile note above), so `pnpm typecheck`, `pnpm lint`, and `pnpm test:studio` were not run, and neither was `supabase start`. CI on this PR is the only signal. **Call-site compatibility check.** CLI 2.99/2.100 moved to a new TypeScript shell with a stricter argument parser: command-specific flags must now come *after* the subcommand. Both call sites in the root `package.json` already use that order, so no script changes are needed: ``` supabase stop --all --no-backup --workdir ./e2e/studio supabase start --exclude studio,mailpit --workdir ./e2e/studio ``` **Changelog entries between 2.76.14 and 2.114.0 that touch `supabase start` or local config.** Listed so reviewers know what changed in the range — **not** as a claim about what is failing in CI: - **2.112.0** — `supabase start` no longer hangs when analytics migrations fail; the analytics container exits and retries instead of booting against an unmigrated database ([#6093](https://github.com/supabase/cli/pull/6093)). - **2.112.0** — `supabase start` reuses existing volumes instead of failing when they already exist ([#6037](https://github.com/supabase/cli/pull/6037)); Kong reloads after `supabase db reset` ([#6017](https://github.com/supabase/cli/pull/6017)); custom auth email templates survive `db reset` ([#6065](https://github.com/supabase/cli/pull/6065)); `supabase start` works on SELinux-enforcing hosts ([#6000](https://github.com/supabase/cli/pull/6000)). - **2.106.0 — behavior change worth watching.** `[api].auto_expose_new_tables` now resolves to `false` when unset, and local start/reset revokes default Data API privileges for newly created `public` tables, sequences, and functions ([#5524](https://github.com/supabase/cli/pull/5524)). Neither `supabase/config.toml` nor `e2e/studio/supabase/config.toml` sets this key, so this default applies. If E2E specs create `public` objects and then read them through the Data API, they may need explicit `GRANT`s (the deprecated escape hatch is `auto_expose_new_tables = true`). - **2.106.0** — when the CLI detects a coding-agent environment, or `--agent yes` is passed, commands default to JSON output ([#5532](https://github.com/supabase/cli/pull/5532)). `e2e:setup:cli` already passes `--output json` to `supabase status` explicitly, so this should be a no-op here. - **2.100.0** — stricter flag ordering, covered above. - **2.112.0** — `functions deploy` no longer forwards `NPM_AUTH_TOKEN` into Docker bundling ([#6005](https://github.com/supabase/cli/pull/6005)). Not used by these workflows. - **2.107.0** — pg-delta is the default schema diff engine for `db diff` / `db pull` on new projects ([#5511](https://github.com/supabase/cli/pull/5511)). - Many bundled Docker image bumps across the range (`supabase/postgres` 17.6.1.087 → later patches, `postgres-meta`, `vector` 0.28.1 → 0.53.0, Studio image), plus `fix(analytics): wait for logflare before starting vector` (2.84.3) and `fix: use correct docker.sock binding with vector` (2.84.7). Full comparison: https://github.com/supabase/cli/compare/v2.76.14...v2.114.0 --- _Generated by [Claude Code](https://claude.ai/code/session_0143DrDMGnSSwuHebTPJv7ZY)_ --------- Co-authored-by: Claude Co-authored-by: Ivan Vasilov --- e2e/studio/features/wrappers.spec.ts | 6 +- e2e/studio/supabase/config.toml | 2 + package.json | 2 +- pnpm-lock.yaml | 230 ++++++++++++++++----------- pnpm-workspace.yaml | 1 - 5 files changed, 144 insertions(+), 97 deletions(-) diff --git a/e2e/studio/features/wrappers.spec.ts b/e2e/studio/features/wrappers.spec.ts index bfe7d73fd1023..49d098fb9a84c 100644 --- a/e2e/studio/features/wrappers.spec.ts +++ b/e2e/studio/features/wrappers.spec.ts @@ -18,7 +18,7 @@ testRunner('Stripe', () => { create extension if not exists wrappers schema extensions - version '0.5.7' + version '0.6.2' cascade; `) }, @@ -59,7 +59,7 @@ testRunner('Stripe', () => { create extension if not exists wrappers schema extensions - version '0.5.7' + version '0.6.2' cascade; `) }, @@ -102,7 +102,7 @@ testRunner('S3 Wrapper', () => { create extension if not exists wrappers schema extensions - version '0.5.7' + version '0.6.2' cascade; `) }, diff --git a/e2e/studio/supabase/config.toml b/e2e/studio/supabase/config.toml index 2eeac96eadd6a..524c17b04e959 100644 --- a/e2e/studio/supabase/config.toml +++ b/e2e/studio/supabase/config.toml @@ -16,6 +16,8 @@ extra_search_path = ["public", "extensions"] # The maximum number of rows returned from a view, table, or function. Limits payload size # for accidental or malicious requests. max_rows = 1000 +# backward-compatible, to automatically expose newly created tables to PostgREST +auto_expose_new_tables = true [api.tls] # Enable HTTPS endpoints locally using a self-signed certificate. diff --git a/package.json b/package.json index 35774f9083a30..3fb61124c179d 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,7 @@ "prettier": "^3.8.0", "prettier-plugin-sql-cst": "^0.18.0", "rimraf": "^6.0.0", - "supabase": "^2.76.10", + "supabase": "^2.114.0", "supports-color": "^8.0.0", "tailwindcss": "catalog:", "tsx": "catalog:", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4f261aae08405..305d81153a8a9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -123,7 +123,6 @@ overrides: postcss: ^8.5.18 qs: ^6.15.2 refractor>prismjs: ^1.30.0 - supabase>tar: ^7.5.21 tmp: ^0.2.7 vite>esbuild: ^0.28.1 webpack: ^5.104.1 @@ -165,8 +164,8 @@ importers: specifier: ^6.0.0 version: 6.0.1 supabase: - specifier: ^2.76.10 - version: 2.76.14(supports-color@8.1.1) + specifier: ^2.114.0 + version: 2.114.0 supports-color: specifier: ^8.0.0 version: 8.1.1 @@ -594,7 +593,7 @@ importers: version: 7.29.7(supports-color@8.1.1) '@graphiql/toolkit': specifier: ^0.9.1 - version: 0.9.1(@types/node@22.13.14)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.21.3))(graphql@16.11.0) + version: 0.9.1(@types/node@22.13.14)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.21.0))(graphql@16.11.0) '@graphql-codegen/cli': specifier: 5.0.5 version: 5.0.5(@parcel/watcher@2.5.6)(@types/node@22.13.14)(encoding@0.1.13)(graphql-sock@1.0.1(graphql@16.11.0))(graphql@16.11.0)(supports-color@8.1.1)(typescript@6.0.2) @@ -675,7 +674,7 @@ importers: version: 13.2.2 graphiql: specifier: ^4.0.2 - version: 4.0.2(@codemirror/language@6.11.0)(@emotion/is-prop-valid@1.4.0)(@types/node@22.13.14)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.21.3))(graphql@16.11.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + version: 4.0.2(@codemirror/language@6.11.0)(@emotion/is-prop-valid@1.4.0)(@types/node@22.13.14)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.21.0))(graphql@16.11.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) npm-run-all: specifier: ^4.1.5 version: 4.1.5 @@ -3665,6 +3664,12 @@ packages: peerDependencies: '@noble/ciphers': ^1.0.0 + '@ecies/ciphers@0.2.6': + resolution: {integrity: sha512-patgsRPKGkhhoBjETV4XxD0En4ui5fbX0hzayqI3M8tvNMGUoUvmyYAIWwlxBc1KX5cturfqByYdj5bYGRpN9g==} + engines: {bun: '>=1', deno: '>=2.7.10', node: '>=16'} + peerDependencies: + '@noble/ciphers': ^1.0.0 + '@edge-runtime/cookies@5.0.2': resolution: {integrity: sha512-Sd8LcWpZk/SWEeKGE8LT6gMm5MGfX/wm+GPnh1eBEtCpya3vYqn37wYknwAHw92ONoyyREl1hJwxV/Qx2DWNOg==} engines: {node: '>=16'} @@ -7461,6 +7466,50 @@ packages: resolution: {integrity: sha512-NA0rsgAlWZPvbhw8aUdmgfpHVgUAcd8zK5ov43l++o1bLIPXZhRiAlRobhwF5AatQuovpqxsMH50F4oyyV4XZw==} engines: {node: '>=22.0.0'} + '@supabase/cli-darwin-arm64@2.114.0': + resolution: {integrity: sha512-zdZA+hf8W2sMDQkWWJKYcgOS6AAMsc8oMTaspfYrga41paUqSvuXZ4uAhBaNm3DiNeJmQ0ddb9EaLo0lPxaQpw==} + cpu: [arm64] + os: [darwin] + + '@supabase/cli-darwin-x64@2.114.0': + resolution: {integrity: sha512-r89Go+y0IyBS+8pHsBZcs9s1W7xB8IL60k97INn0EYgPlE99Bgy7aZF+FQd+c1e3RAXLPS4/Znf7fheXKuNCtg==} + cpu: [x64] + os: [darwin] + + '@supabase/cli-linux-arm64-musl@2.114.0': + resolution: {integrity: sha512-G5/97+h2CXzoK95jNlWY8CyOt+Zm0k3A9/d1r/jDYoB/y1P5cQmK1TYPPfI+ErWYQeoiQ6okfftAAg8bZEGqcA==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@supabase/cli-linux-arm64@2.114.0': + resolution: {integrity: sha512-vYFAP+gI6BOcwjZ+Zs9kpqtUt3KTE5kL56H65CTCdwRBJ/fXU+vCW3WvhLyFmKmJ8sDzjCvMcHwuhjKPbTmM7A==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@supabase/cli-linux-x64-musl@2.114.0': + resolution: {integrity: sha512-87e8MzkKZryXOsmMSbrS4b4jX95uPohS/AplJb2QWwTzLuOXZ+G60e0NR9tkuYYc7TIrDAHhFxcGaQweRIERug==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@supabase/cli-linux-x64@2.114.0': + resolution: {integrity: sha512-MKgeafQEWYVKCtJgOgGJo1SqiKI4dd5eN460FbLOg01j1sYh7zGqhHk8WxF3enc4WeFQpVMpv147UyrOueHheg==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@supabase/cli-windows-arm64@2.114.0': + resolution: {integrity: sha512-nhF/H5tPs0YsuKcdX5gGV6Ku7pFpSobXubt0y3rHfmR4itG6hhGE2We1hCyc7it0ozlDsA2NxOb1m9YlmY38+A==} + cpu: [arm64] + os: [win32] + + '@supabase/cli-windows-x64@2.114.0': + resolution: {integrity: sha512-JtdgR9HvhgUxyd+GMpTECHCSFbAFP67/FJIWybYOVt5Qh6g4rqlLSWmT5zWFCQBSBw0dSw7xf2V3YB1+HESmiA==} + cpu: [x64] + os: [win32] + '@supabase/functions-js@2.112.3': resolution: {integrity: sha512-gfv481mTOVWtZIJgXupxZpni2V2UWPf6jeF/jOK7HdMHdH+mt6sU0sHHwf0POsPip8ltlulu9OUHgwVzl5ddRw==} engines: {node: '>=22.0.0'} @@ -9497,10 +9546,6 @@ packages: big.js@5.2.2: resolution: {integrity: sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==} - bin-links@6.0.0: - resolution: {integrity: sha512-X4CiKlcV2GjnCMwnKAfbVWpHa++65th9TuzAEYtZoATiOE2DQKhSp4CJlyLoTqdhBKlXjpXjCTYPNNFS33Fi6w==} - engines: {node: ^20.17.0 || >=22.9.0} - binary-extensions@2.2.0: resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} engines: {node: '>=8'} @@ -9849,10 +9894,6 @@ packages: resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==} engines: {node: '>=0.10.0'} - cmd-shim@8.0.0: - resolution: {integrity: sha512-Jk/BK6NCapZ58BKUxlSI+ouKRbjH1NLZCgJkYoab+vEHUY3f6OzpNBN9u7HFSv9J6TRDGs4PLOHezoKGaFRSCA==} - engines: {node: ^20.17.0 || >=22.9.0} - cmdk@1.1.1: resolution: {integrity: sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg==} peerDependencies: @@ -10713,6 +10754,10 @@ packages: resolution: {integrity: sha512-r6kEJXDKecVOCj2nLMuXK/FCPeurW33+3JRpfXVbjLja3XUYFfD9I/JBreH6sUyzcm3G/YQboBjMla6poKeSdA==} engines: {bun: '>=1', deno: '>=2', node: '>=16'} + eciesjs@0.5.0: + resolution: {integrity: sha512-s0J9SEVYAEPg7J63GFMApLYzPH9VNIQIyC6s15JpnqVc0TqcKWdbgFlnAweEBRyMmko2dcs2sfC83Hj4J43tuA==} + engines: {bun: '>=1', deno: '>=2.7.10', node: '>=16'} + ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} @@ -12691,6 +12736,9 @@ packages: jose@6.1.3: resolution: {integrity: sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==} + jose@6.2.9: + resolution: {integrity: sha512-XrchZOFZUl/T3vTwRe8XK+cJrGtMF4th1ARnDfwbBXFKThGhlsxEE4Zu03AD/bjJSt/9jT/mxrOCkJWOg77aPA==} + jotai@2.8.1: resolution: {integrity: sha512-Gmk5Y3yJL/vN5S0rQ6AaWpXH5Q+HBGHThMHXfylVzXGVuO8YxPRtZf8Y9XYvl+h7ZMQXoHNdFi37vNsJFsiszQ==} engines: {node: '>=12.20.0'} @@ -12820,6 +12868,7 @@ packages: jsrsasign@11.1.1: resolution: {integrity: sha512-6w95OOXH8DNeGxakqLndBEqqwQ6A70zGaky1oxfg8WVLWOnghTfJsc5Tknx+Z88MHSb1bGLcqQHImOF8Lk22XA==} + deprecated: This package is no longer maintained. jsx-ast-utils@3.3.5: resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} @@ -14179,10 +14228,6 @@ packages: nostics@1.2.0: resolution: {integrity: sha512-FGqEfhQjrvo1lL8KFifdTQiNwwQHJxC1jtYE1Rc54qF/jxONUNL+kC9gS1krX8Q65PgrQ5fCqH/I4NhWBvdSqg==} - npm-normalize-package-bin@5.0.0: - resolution: {integrity: sha512-CJi3OS4JLsNMmr2u07OJlhcrPxCeOeP/4xq67aWNai6TNWWbTrlNDgl8NcFKVlcBKp18GPj+EzbNIgrBfZhsag==} - engines: {node: ^20.17.0 || >=22.9.0} - npm-run-all@4.1.5: resolution: {integrity: sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==} engines: {node: '>= 4'} @@ -15052,10 +15097,6 @@ packages: resolution: {integrity: sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - proc-log@6.0.0: - resolution: {integrity: sha512-KG/XsTDN901PNfPfAMmj6N/Ywg9tM+bHK8pAz+27fS4N4Pcr+4zoYBOcGSBu6ceXYNPxkLpa4ohtfxV1XcLAfA==} - engines: {node: ^20.17.0 || >=22.9.0} - process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} @@ -15406,10 +15447,6 @@ packages: resolution: {integrity: sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==} engines: {node: '>=0.10.0'} - read-cmd-shim@6.0.0: - resolution: {integrity: sha512-1zM5HuOfagXCBWMN83fuFI/x+T/UhZ7k+KIzhrHXcQoeX5+7gmaDYjELQHmmzIodumBHeByBJT4QYS7ufAgs7A==} - engines: {node: ^20.17.0 || >=22.9.0} - read-pkg@3.0.0: resolution: {integrity: sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==} engines: {node: '>=4'} @@ -16424,9 +16461,8 @@ packages: resolution: {integrity: sha512-CWGAczaNXTUDExaxi+hUEPqlPtCiZ4r3a74NXOx7+Sf+P75foCuX/aXL9MztosWwRnZZAI8VGUXpdNe6K3xTtA==} engines: {node: '>=18.0.0'} - supabase@2.76.14: - resolution: {integrity: sha512-2XmYs8+A4WXd+w/OND9u9qbSTnGdLCuddnii01H1LkmgwcZ9krXwxElE+YYmzhsEKCUHv5wVjAf5HTUwQ4PnVA==} - engines: {npm: '>=8'} + supabase@2.114.0: + resolution: {integrity: sha512-JtCR+eDgVs2YtmZl5THdkaTeXex80lax+3V2SXRSuj280SWJPTIKxMLc82iDKjZlqXnt9zrGnJkrWb7DwHGPhQ==} hasBin: true supports-color@10.0.0: @@ -17768,10 +17804,6 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - write-file-atomic@7.0.0: - resolution: {integrity: sha512-YnlPC6JqnZl6aO4uRc+dx5PHguiR9S6WeoLtpxNT9wIG+BDya7ZNE1q7KOjVgaA73hKhKLpVPgJ5QA9THQ5BRg==} - engines: {node: ^20.17.0 || >=22.9.0} - ws@7.5.11: resolution: {integrity: sha512-zS54Oen9bITtp7kp2XM3AydrCIq1D+HwJOuH+c+e4LfpL/lotP5osijd+UoMnxwAam1GN8R4KtLAyIrIcBNpiA==} engines: {node: '>=8.3.0'} @@ -18429,7 +18461,7 @@ snapshots: '@aws-sdk/types': 3.973.8 '@smithy/property-provider': 4.2.14 '@smithy/shared-ini-file-loader': 4.4.9 - '@smithy/types': 4.14.1 + '@smithy/types': 4.16.1 tslib: 2.8.1 transitivePeerDependencies: - aws-crt @@ -18485,16 +18517,16 @@ snapshots: '@aws-sdk/core': 3.974.8 '@aws-sdk/types': 3.973.8 '@aws-sdk/util-arn-parser': 3.972.3 - '@smithy/core': 3.23.17 + '@smithy/core': 3.31.1 '@smithy/node-config-provider': 4.3.14 '@smithy/protocol-http': 5.3.14 '@smithy/signature-v4': 5.3.14 '@smithy/smithy-client': 4.12.13 - '@smithy/types': 4.14.1 + '@smithy/types': 4.16.1 '@smithy/util-config-provider': 4.2.2 '@smithy/util-middleware': 4.2.14 '@smithy/util-stream': 4.5.25 - '@smithy/util-utf8': 4.2.2 + '@smithy/util-utf8': 4.4.16 tslib: 2.8.1 '@aws-sdk/middleware-user-agent@3.972.38': @@ -18524,7 +18556,7 @@ snapshots: '@aws-sdk/util-user-agent-browser': 3.972.10 '@aws-sdk/util-user-agent-node': 3.973.24 '@smithy/config-resolver': 4.4.17 - '@smithy/core': 3.23.17 + '@smithy/core': 3.31.1 '@smithy/fetch-http-handler': 5.3.17 '@smithy/hash-node': 4.2.14 '@smithy/invalid-dependency': 4.2.14 @@ -18537,7 +18569,7 @@ snapshots: '@smithy/node-http-handler': 4.6.1 '@smithy/protocol-http': 5.3.14 '@smithy/smithy-client': 4.12.13 - '@smithy/types': 4.14.1 + '@smithy/types': 4.16.1 '@smithy/url-parser': 4.2.14 '@smithy/util-base64': 4.3.2 '@smithy/util-body-length-browser': 4.2.2 @@ -18547,7 +18579,7 @@ snapshots: '@smithy/util-endpoints': 3.4.2 '@smithy/util-middleware': 4.2.14 '@smithy/util-retry': 4.3.8 - '@smithy/util-utf8': 4.2.2 + '@smithy/util-utf8': 4.4.16 tslib: 2.8.1 transitivePeerDependencies: - aws-crt @@ -18566,7 +18598,7 @@ snapshots: '@aws-sdk/types': 3.973.8 '@smithy/protocol-http': 5.3.14 '@smithy/signature-v4': 5.3.14 - '@smithy/types': 4.14.1 + '@smithy/types': 4.16.1 tslib: 2.8.1 '@aws-sdk/token-providers@3.1041.0': @@ -19356,6 +19388,10 @@ snapshots: dependencies: '@noble/ciphers': 1.3.0 + '@ecies/ciphers@0.2.6(@noble/ciphers@1.3.0)': + dependencies: + '@noble/ciphers': 1.3.0 + '@edge-runtime/cookies@5.0.2': {} '@effect-ts/core@0.60.5': @@ -19614,9 +19650,9 @@ snapshots: '@floating-ui/utils@0.2.9': {} - '@graphiql/plugin-doc-explorer@0.0.1(@codemirror/language@6.11.0)(@emotion/is-prop-valid@1.4.0)(@types/node@22.13.14)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.21.3))(graphql@16.11.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@graphiql/plugin-doc-explorer@0.0.1(@codemirror/language@6.11.0)(@emotion/is-prop-valid@1.4.0)(@types/node@22.13.14)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.21.0))(graphql@16.11.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@graphiql/react': 0.32.0(@codemirror/language@6.11.0)(@emotion/is-prop-valid@1.4.0)(@types/node@22.13.14)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.21.3))(graphql@16.11.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@graphiql/react': 0.32.0(@codemirror/language@6.11.0)(@emotion/is-prop-valid@1.4.0)(@types/node@22.13.14)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.21.0))(graphql@16.11.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@headlessui/react': 2.2.2(react-dom@19.2.6(react@19.2.6))(react@19.2.6) graphql: 16.11.0 react: 19.2.6 @@ -19644,10 +19680,10 @@ snapshots: - immer - use-sync-external-store - '@graphiql/plugin-history@0.0.2(@codemirror/language@6.11.0)(@emotion/is-prop-valid@1.4.0)(@types/node@22.13.14)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.21.3))(graphql@16.11.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@graphiql/plugin-history@0.0.2(@codemirror/language@6.11.0)(@emotion/is-prop-valid@1.4.0)(@types/node@22.13.14)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.21.0))(graphql@16.11.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@graphiql/react': 0.32.0(@codemirror/language@6.11.0)(@emotion/is-prop-valid@1.4.0)(@types/node@22.13.14)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.21.3))(graphql@16.11.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@graphiql/toolkit': 0.11.3(@types/node@22.13.14)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.21.3))(graphql@16.11.0) + '@graphiql/react': 0.32.0(@codemirror/language@6.11.0)(@emotion/is-prop-valid@1.4.0)(@types/node@22.13.14)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.21.0))(graphql@16.11.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@graphiql/toolkit': 0.11.3(@types/node@22.13.14)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.21.0))(graphql@16.11.0) react: 19.2.6 react-compiler-runtime: 19.1.0-rc.1(react@19.2.6) react-dom: 19.2.6(react@19.2.6) @@ -19676,9 +19712,9 @@ snapshots: - immer - use-sync-external-store - '@graphiql/react@0.32.0(@codemirror/language@6.11.0)(@emotion/is-prop-valid@1.4.0)(@types/node@22.13.14)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.21.3))(graphql@16.11.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@graphiql/react@0.32.0(@codemirror/language@6.11.0)(@emotion/is-prop-valid@1.4.0)(@types/node@22.13.14)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.21.0))(graphql@16.11.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@graphiql/toolkit': 0.11.3(@types/node@22.13.14)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.21.3))(graphql@16.11.0) + '@graphiql/toolkit': 0.11.3(@types/node@22.13.14)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.21.0))(graphql@16.11.0) '@radix-ui/react-dialog': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-dropdown-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@radix-ui/react-tooltip': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) @@ -19746,23 +19782,23 @@ snapshots: transitivePeerDependencies: - '@types/node' - '@graphiql/toolkit@0.11.3(@types/node@22.13.14)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.21.3))(graphql@16.11.0)': + '@graphiql/toolkit@0.11.3(@types/node@22.13.14)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.21.0))(graphql@16.11.0)': dependencies: '@n1ru4l/push-pull-async-iterable-iterator': 3.2.0 graphql: 16.11.0 meros: 1.3.0(@types/node@22.13.14) optionalDependencies: - graphql-ws: 6.0.4(graphql@16.11.0)(ws@8.21.3) + graphql-ws: 6.0.4(graphql@16.11.0)(ws@8.21.0) transitivePeerDependencies: - '@types/node' - '@graphiql/toolkit@0.9.1(@types/node@22.13.14)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.21.3))(graphql@16.11.0)': + '@graphiql/toolkit@0.9.1(@types/node@22.13.14)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.21.0))(graphql@16.11.0)': dependencies: '@n1ru4l/push-pull-async-iterable-iterator': 3.2.0 graphql: 16.11.0 meros: 1.3.0(@types/node@22.13.14) optionalDependencies: - graphql-ws: 6.0.4(graphql@16.11.0)(ws@8.21.3) + graphql-ws: 6.0.4(graphql@16.11.0)(ws@8.21.0) transitivePeerDependencies: - '@types/node' @@ -23581,7 +23617,7 @@ snapshots: '@smithy/property-provider@4.2.14': dependencies: - '@smithy/types': 4.14.1 + '@smithy/types': 4.16.1 tslib: 2.8.1 '@smithy/protocol-http@5.3.14': @@ -23606,7 +23642,7 @@ snapshots: '@smithy/shared-ini-file-loader@4.4.9': dependencies: - '@smithy/types': 4.14.1 + '@smithy/types': 4.16.1 tslib: 2.8.1 '@smithy/signature-v4@5.3.14': @@ -23833,6 +23869,30 @@ snapshots: dependencies: tslib: 2.8.1 + '@supabase/cli-darwin-arm64@2.114.0': + optional: true + + '@supabase/cli-darwin-x64@2.114.0': + optional: true + + '@supabase/cli-linux-arm64-musl@2.114.0': + optional: true + + '@supabase/cli-linux-arm64@2.114.0': + optional: true + + '@supabase/cli-linux-x64-musl@2.114.0': + optional: true + + '@supabase/cli-linux-x64@2.114.0': + optional: true + + '@supabase/cli-windows-arm64@2.114.0': + optional: true + + '@supabase/cli-windows-x64@2.114.0': + optional: true + '@supabase/functions-js@2.112.3': dependencies: tslib: 2.8.1 @@ -26151,14 +26211,6 @@ snapshots: big.js@5.2.2: {} - bin-links@6.0.0: - dependencies: - cmd-shim: 8.0.0 - npm-normalize-package-bin: 5.0.0 - proc-log: 6.0.0 - read-cmd-shim: 6.0.0 - write-file-atomic: 7.0.0 - binary-extensions@2.2.0: {} binary-search@1.3.6: {} @@ -26600,8 +26652,6 @@ snapshots: cluster-key-slot@1.1.2: {} - cmd-shim@8.0.0: {} - cmdk@1.1.1(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6): dependencies: '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.6) @@ -27471,6 +27521,13 @@ snapshots: '@noble/curves': 1.9.7 '@noble/hashes': 1.8.0 + eciesjs@0.5.0: + dependencies: + '@ecies/ciphers': 0.2.6(@noble/ciphers@1.3.0) + '@noble/ciphers': 1.3.0 + '@noble/curves': 1.9.7 + '@noble/hashes': 1.8.0 + ee-first@1.1.1: {} ejs@3.1.10: @@ -28778,11 +28835,11 @@ snapshots: graceful-fs@4.2.11: {} - graphiql@4.0.2(@codemirror/language@6.11.0)(@emotion/is-prop-valid@1.4.0)(@types/node@22.13.14)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.21.3))(graphql@16.11.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6): + graphiql@4.0.2(@codemirror/language@6.11.0)(@emotion/is-prop-valid@1.4.0)(@types/node@22.13.14)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.21.0))(graphql@16.11.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6): dependencies: - '@graphiql/plugin-doc-explorer': 0.0.1(@codemirror/language@6.11.0)(@emotion/is-prop-valid@1.4.0)(@types/node@22.13.14)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.21.3))(graphql@16.11.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@graphiql/plugin-history': 0.0.2(@codemirror/language@6.11.0)(@emotion/is-prop-valid@1.4.0)(@types/node@22.13.14)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.21.3))(graphql@16.11.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@graphiql/react': 0.32.0(@codemirror/language@6.11.0)(@emotion/is-prop-valid@1.4.0)(@types/node@22.13.14)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.21.3))(graphql@16.11.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@graphiql/plugin-doc-explorer': 0.0.1(@codemirror/language@6.11.0)(@emotion/is-prop-valid@1.4.0)(@types/node@22.13.14)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.21.0))(graphql@16.11.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@graphiql/plugin-history': 0.0.2(@codemirror/language@6.11.0)(@emotion/is-prop-valid@1.4.0)(@types/node@22.13.14)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.21.0))(graphql@16.11.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@graphiql/react': 0.32.0(@codemirror/language@6.11.0)(@emotion/is-prop-valid@1.4.0)(@types/node@22.13.14)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(graphql-ws@6.0.4(graphql@16.11.0)(ws@8.21.0))(graphql@16.11.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) graphql: 16.11.0 react: 19.2.6 react-compiler-runtime: 19.1.0-rc.1(react@19.2.6) @@ -28881,13 +28938,6 @@ snapshots: optionalDependencies: ws: 8.21.0 - graphql-ws@6.0.4(graphql@16.11.0)(ws@8.21.3): - dependencies: - graphql: 16.11.0 - optionalDependencies: - ws: 8.21.3 - optional: true - graphql@16.11.0: {} gray-matter@2.1.1: @@ -29765,6 +29815,8 @@ snapshots: jose@6.1.3: {} + jose@6.2.9: {} + jotai@2.8.1(@types/react@19.2.14)(react@19.2.6): optionalDependencies: '@types/react': 19.2.14 @@ -31930,8 +31982,6 @@ snapshots: nostics@1.2.0: {} - npm-normalize-package-bin@5.0.0: {} - npm-run-all@4.1.5: dependencies: ansi-styles: 3.2.1 @@ -32891,8 +32941,6 @@ snapshots: proc-log@4.2.0: {} - proc-log@6.0.0: {} - process-nextick-args@2.0.1: {} process@0.11.10: {} @@ -33333,8 +33381,6 @@ snapshots: react@19.2.6: {} - read-cmd-shim@6.0.0: {} - read-pkg@3.0.0: dependencies: load-json-file: 4.0.0 @@ -34702,14 +34748,19 @@ snapshots: - debug - supports-color - supabase@2.76.14(supports-color@8.1.1): + supabase@2.114.0: dependencies: - bin-links: 6.0.0 - https-proxy-agent: 7.0.6(supports-color@8.1.1) - node-fetch: 3.3.2 - tar: 7.5.21 - transitivePeerDependencies: - - supports-color + eciesjs: 0.5.0 + jose: 6.2.9 + optionalDependencies: + '@supabase/cli-darwin-arm64': 2.114.0 + '@supabase/cli-darwin-x64': 2.114.0 + '@supabase/cli-linux-arm64': 2.114.0 + '@supabase/cli-linux-arm64-musl': 2.114.0 + '@supabase/cli-linux-x64': 2.114.0 + '@supabase/cli-linux-x64-musl': 2.114.0 + '@supabase/cli-windows-arm64': 2.114.0 + '@supabase/cli-windows-x64': 2.114.0 supports-color@10.0.0: {} @@ -36175,11 +36226,6 @@ snapshots: wrappy@1.0.2: {} - write-file-atomic@7.0.0: - dependencies: - imurmurhash: 0.1.4 - signal-exit: 4.1.0 - ws@7.5.11: {} ws@8.21.0: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 8d678248a5607..6bf4113c0aacf 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -114,7 +114,6 @@ overrides: postcss: 'catalog:' qs: ^6.15.2 refractor>prismjs: ^1.30.0 - supabase>tar: ^7.5.21 tmp: ^0.2.7 vite>esbuild: ^0.28.1 webpack: ^5.104.1 From 6fd48944a8a8b55143eee2dd5b5730624b9b4a9d Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Wed, 19 Aug 2026 16:58:35 +0800 Subject: [PATCH 7/9] Support multi series bar charts in explorer and chart-bar (#49241) ## Context - Updates the BarChart in our design system to support multi series in a similar fashion to how the LineChart already supports multi series - Update chart renderer in explorer notebooks to support multiple Y axes using the `MultiSelector` component - Up to 3 y columns can be selected for now (Arbitrary limit from a color's selection POV but also just felt like anything more and the chart doesn't feel useful) - Only linear scale will be supported if multiple y columns are selected (Will switch back to linear if originally on log scale) image image ## Summary by CodeRabbit * **New Features** * Charts now support selecting and displaying up to three Y-axis data series. * Bar and line charts render multiple series with distinct colors. * Cumulative calculations work independently across multiple selected series. * Chart controls provide clearer responsive layouts and limit selections appropriately. * **Bug Fixes** * Logarithmic scaling automatically switches to linear when multiple series or unsupported values are selected. --- .../QueryEditor/DisplaySettingsButton.tsx | 100 ++++++++++++------ .../Explorer/QueryEditor/QueryResultChart.tsx | 51 ++++++--- .../ui/QueryBlock/QueryBlock.utils.ts | 18 ++-- .../data/content/notebooks/notebook-schema.ts | 4 +- .../ui/QueryBlock/QueryBlock.utils.test.ts | 16 +++ .../src/Chart/charts/chart-bar.tsx | 48 ++++++--- 6 files changed, 170 insertions(+), 67 deletions(-) diff --git a/apps/studio/components/interfaces/Explorer/QueryEditor/DisplaySettingsButton.tsx b/apps/studio/components/interfaces/Explorer/QueryEditor/DisplaySettingsButton.tsx index b8eb3785b0e6b..e093b3c2ddcac 100644 --- a/apps/studio/components/interfaces/Explorer/QueryEditor/DisplaySettingsButton.tsx +++ b/apps/studio/components/interfaces/Explorer/QueryEditor/DisplaySettingsButton.tsx @@ -18,11 +18,18 @@ import { TooltipTrigger, } from 'ui' import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' +import { + MultiSelector, + MultiSelectorContent, + MultiSelectorItem, + MultiSelectorList, + MultiSelectorTrigger, +} from 'ui-patterns/multi-select' import { ExplorerToolbarAction } from '../ExplorerToolbar' import { type QueryDisplay, type QueryResult } from '../types' import { checkHasNonPositiveValues } from '@/components/ui/QueryBlock/QueryBlock.utils' -import { type ChartConfig } from '@/data/content/notebooks/notebook-schema' +import { MAX_CHART_Y_COLUMNS, type ChartConfig } from '@/data/content/notebooks/notebook-schema' interface DisplaySettingsButtonProps { display: QueryDisplay @@ -32,9 +39,11 @@ interface DisplaySettingsButtonProps { onChange: (display: QueryDisplay) => void } -// [Joshen] TODO support multiple y axis charts -// [Joshen] TODO onUpdateChartConfig can likely be shifted into the notebook-state -// so this component doesn't need to know about other cells +const getLogScaleDisabledReason = (y_columns: string[]) => { + if (y_columns.length === 0) return 'Select a column for the Y axis first' + if (y_columns.length > 1) return 'Only available with a single Y axis column' + return 'Data contains zero or negative values' +} export const DisplaySettingsButton = ({ display, @@ -58,8 +67,9 @@ export const DisplaySettingsButton = ({ [result, y_columns] ) + // Logarithmic scale only applies to a single series. const canToggleLogScale = useMemo(() => { - if (y_columns.length === 0 || !result || (result.rows ?? []).length === 0) return false + if (y_columns.length !== 1 || !result || (result.rows ?? []).length === 0) return false return !hasNonPositiveValues }, [hasNonPositiveValues, result, y_columns.length]) @@ -87,17 +97,17 @@ export const DisplaySettingsButton = ({ }) useEffect(() => { - if (hasNonPositiveValues && scale === 'log') { + if (scale === 'log' && (hasNonPositiveValues || y_columns.length > 1)) { resetToLinearScale() } - }, [hasNonPositiveValues, scale]) + }, [hasNonPositiveValues, scale, y_columns.length]) return ( } tooltip="Result settings" /> - +

Result display settings @@ -147,12 +157,17 @@ export const DisplaySettingsButton = ({

- + - - + + + + {columns.map((x) => ( + = MAX_CHART_Y_COLUMNS && !y_columns.includes(x) + } + > + {x} + + ))} + + + - +