From 4ee43f3585fd26abaa9dea03c8e1a680a5038f5c Mon Sep 17 00:00:00 2001 From: Saxon Fletcher Date: Thu, 3 Sep 2026 09:44:45 +1000 Subject: [PATCH 1/4] chore(studio): refine Explorer query UI (#49895) 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? UI refinements for Explorer query surfaces. ## What is the current behavior? - The assistant chat textarea uses a tighter radius than the Run SQL / Create a notebook cards on Explorer home. - Chart results sit unevenly in the results pane because axis gutters stack on card padding, and long Y labels can clip. - Selecting SQL in the editor changes the primary Run button to Run selected, which is easy to trigger by accident. - The Prettify SQL icon in notebook query cells uses Lucide's default size, so it doesn't match other toolbar actions. ## What is the new behavior? - Assistant chat form uses `rounded-lg` so it matches the home action cards everywhere the form is used. - Query result charts collapse unused axis space, add a little padding when labels are on, and size the Y axis from formatted ticks so longer labels fit. - Run is a default split button that always executes the full query. Run selected is a secondary menu item, disabled until SQL is selected. - Notebook cell Prettify SQL icons use `size={16}` and `strokeWidth={2}` like the rest of the Explorer toolbar. ## Additional context Cmd+Enter in the editor still runs the current selection when there is one. ## Test plan - [ ] Open Explorer home and confirm the assistant chat radius matches the Run SQL and Create a notebook cards. - [ ] Run a query, switch to chart view, and check spacing with labels off and on, including large Y values. - [ ] With no selection, click Run and confirm the full query runs. Open the split menu and confirm Run selected is disabled. - [ ] Select SQL, click Run, and confirm the full query still runs. Use Run selected from the menu to run only the selection. - [ ] In a notebook query cell, confirm Prettify SQL matches the size and stroke of nearby toolbar icons. Made with [Cursor](https://cursor.com) ## Summary by CodeRabbit - **New Features** - Added a split Run control in the query editor, with separate actions for running all content or only selected text. - Added support for customizing chart X-axis display settings. - Improved chart Y-axis sizing, scaling, and tick formatting for clearer results. - **Bug Fixes** - The “Run selected” action is unavailable when no text is selected. - **Style** - Updated toolbar icon sizing and added rounded corners to the assistant chat input. --- .../interfaces/Explorer/QueryCell/index.tsx | 2 +- .../Explorer/QueryEditor/QueryResultChart.tsx | 37 +++++--- .../Explorer/QueryEditor/QueryRunButton.tsx | 84 +++++++++++++++++++ .../interfaces/Explorer/QueryEditor/index.tsx | 24 ++---- .../Explorer/__tests__/QueryTab.test.tsx | 48 +++++++++-- .../ui/AIAssistantPanel/AssistantChatForm.tsx | 2 +- .../src/Chart/charts/chart-bar.tsx | 10 +++ .../src/Chart/charts/chart-line.tsx | 10 +++ 8 files changed, 181 insertions(+), 36 deletions(-) create mode 100644 apps/studio/components/interfaces/Explorer/QueryEditor/QueryRunButton.tsx diff --git a/apps/studio/components/interfaces/Explorer/QueryCell/index.tsx b/apps/studio/components/interfaces/Explorer/QueryCell/index.tsx index cdbff562c8fd1..73df59b831595 100644 --- a/apps/studio/components/interfaces/Explorer/QueryCell/index.tsx +++ b/apps/studio/components/interfaces/Explorer/QueryCell/index.tsx @@ -136,7 +136,7 @@ export const QueryCell = forwardRef(function onDisplayChange={handleDisplayChange} toolbarActions={ } + icon={} tooltip={
Prettify SQL diff --git a/apps/studio/components/interfaces/Explorer/QueryEditor/QueryResultChart.tsx b/apps/studio/components/interfaces/Explorer/QueryEditor/QueryResultChart.tsx index 095ad7af7623b..acfb7cd7e9465 100644 --- a/apps/studio/components/interfaces/Explorer/QueryEditor/QueryResultChart.tsx +++ b/apps/studio/components/interfaces/Explorer/QueryEditor/QueryResultChart.tsx @@ -1,10 +1,15 @@ import { useMemo } from 'react' -import { type ChartConfig as ChartSeriesConfig } from 'ui' +import { cn, type ChartConfig as ChartSeriesConfig } from 'ui' import { Chart, ChartBar, ChartCard, ChartContent, ChartLine } from 'ui-patterns/Chart' import { type QueryResult } from '../types' import NoDataPlaceholder from '@/components/ui/Charts/NoDataPlaceholder' -import { formatLogTick, getCumulativeResults } from '@/components/ui/QueryBlock/QueryBlock.utils' +import { + computeYAxisWidth, + formatLogTick, + formatYAxisTick, + getCumulativeResults, +} from '@/components/ui/QueryBlock/QueryBlock.utils' import { type ChartConfig } from '@/data/content/notebooks/notebook-schema' interface QueryResultChartProps { @@ -58,6 +63,20 @@ export const QueryResultChart = ({ chart, result }: QueryResultChartProps) => { ) const resultToRender = cumulative ? cumulativeResults : chartRows + const yAxisWidth = Math.max( + 36, + ...y_series.map((key) => + computeYAxisWidth(resultToRender, key, { isLogScale: effectiveScale === 'log' }) + ) + ) + + const yAxisProps = { + ...(show_labels ? { width: yAxisWidth } : {}), + scale: effectiveScale === 'log' ? 'log' : 'auto', + domain: effectiveScale === 'log' ? ([1, 'auto'] as const) : undefined, + tickFormatter: effectiveScale === 'log' ? formatLogTick : formatYAxisTick, + } + if (!result || (result?.rows && result.rows.length === 0)) { return ( { return ( - + {type === 'bar' && ( { showXAxis={show_labels} showYAxis={show_labels} data={resultToRender} - YAxisProps={{ - scale: effectiveScale === 'log' ? 'log' : 'auto', - domain: effectiveScale === 'log' ? [1, 'auto'] : undefined, - tickFormatter: effectiveScale === 'log' ? formatLogTick : undefined, - }} + YAxisProps={yAxisProps} /> )} {type === 'line' && ( @@ -113,11 +128,7 @@ export const QueryResultChart = ({ chart, result }: QueryResultChartProps) => { showXAxis={show_labels} showYAxis={show_labels} data={resultToRender} - YAxisProps={{ - scale: effectiveScale === 'log' ? 'log' : 'auto', - domain: effectiveScale === 'log' ? [1, 'auto'] : undefined, - tickFormatter: effectiveScale === 'log' ? formatLogTick : undefined, - }} + YAxisProps={yAxisProps} /> )} diff --git a/apps/studio/components/interfaces/Explorer/QueryEditor/QueryRunButton.tsx b/apps/studio/components/interfaces/Explorer/QueryEditor/QueryRunButton.tsx new file mode 100644 index 0000000000000..bac5df8a5a92c --- /dev/null +++ b/apps/studio/components/interfaces/Explorer/QueryEditor/QueryRunButton.tsx @@ -0,0 +1,84 @@ +import { ChevronDown, Play } from 'lucide-react' +import { + Button, + DropdownMenu, + DropdownMenuContent, + DropdownMenuTrigger, + KeyboardShortcut, +} from 'ui' + +import { ButtonTooltip } from '@/components/ui/ButtonTooltip' +import { DropdownMenuItemTooltip } from '@/components/ui/DropdownMenuItemTooltip' + +interface QueryRunButtonProps { + isExecuting: boolean + disabled: boolean + hasSelection: boolean + onRun: () => void + onRunSelected: () => void +} + +export const QueryRunButton = ({ + isExecuting, + disabled, + hasSelection, + onRun, + onRunSelected, +}: QueryRunButtonProps) => { + return ( +
+ } + className="rounded-r-none hover:z-10 focus-visible:z-10 focus-visible:rounded-r-sm" + onClick={onRun} + tooltip={{ + content: { + side: 'bottom', + text: ( +
+ Run query + +
+ ), + }, + }} + > + Run +
+ + +
+ ) +} diff --git a/apps/studio/components/interfaces/Explorer/QueryEditor/index.tsx b/apps/studio/components/interfaces/Explorer/QueryEditor/index.tsx index 761182822e76d..f8fc71760961e 100644 --- a/apps/studio/components/interfaces/Explorer/QueryEditor/index.tsx +++ b/apps/studio/components/interfaces/Explorer/QueryEditor/index.tsx @@ -1,7 +1,7 @@ import { useMonaco } from '@monaco-editor/react' import { acceptUntrustedSql, untrustedSql, type UntrustedSqlFragment } from '@supabase/pg-meta' import { useFlag } from 'common' -import { CodeSquare, Eye, EyeOff, Play } from 'lucide-react' +import { CodeSquare, Eye, EyeOff } from 'lucide-react' import type { editor as monacoEditor, Selection } from 'monaco-editor' import { forwardRef, @@ -12,7 +12,7 @@ import { useState, type ReactNode, } from 'react' -import { Button, cn, KeyboardShortcut } from 'ui' +import { Button, cn } from 'ui' import { resolveLogTimeRange } from '../../QuerySources/LogTimeRange.utils' import { @@ -32,6 +32,7 @@ import { import { type QueryDisplay, type QueryResult } from '../types' import { DisplaySettingsButton } from './DisplaySettingsButton' import { QueryResultRenderer } from './QueryResultRenderer' +import { QueryRunButton } from './QueryRunButton' import { QuerySourceMenu } from './QuerySourceMenu' import { useQueryEditorAi } from './useQueryEditorAi' import { LegacyLogsRewriteBanner } from '@/components/interfaces/Settings/Logs/LegacyLogsRewriteBanner' @@ -437,26 +438,19 @@ export const QueryEditor = forwardRef(funct {toolbarActions} - } - loading={isExecuting} - tooltip={ -
- {hasSelection ? 'Run selected query' : 'Run query'} - -
- } + { + hasSelection={showQuery && hasSelection} + onRun={() => handleRunQuery({ rawSql: sql })} + onRunSelected={() => { const editorInstance = editorInstanceRef.current const rawSql = editorInstance ? getEditorValueOrSelection(editorInstance) : sql handleRunQuery({ rawSql }) }} - > - {hasSelection ? 'Run selected' : 'Run'} -
+ /> diff --git a/apps/studio/components/interfaces/Explorer/__tests__/QueryTab.test.tsx b/apps/studio/components/interfaces/Explorer/__tests__/QueryTab.test.tsx index a7ddb608be280..e9c2dbf799f4d 100644 --- a/apps/studio/components/interfaces/Explorer/__tests__/QueryTab.test.tsx +++ b/apps/studio/components/interfaces/Explorer/__tests__/QueryTab.test.tsx @@ -429,7 +429,34 @@ describe('QueryTab execution', () => { expect(executedQueries[0]).toContain('ALTER TABLE foo ENABLE ROW LEVEL SECURITY;') }) - it('runs only the selected text, not the full editor content, when there is an active selection', async () => { + it('runs the full query from the Run button even when text is selected', async () => { + createDraft({ _tag: 'database' }, 'select 1;\nselect 2;') + testContext.selectedText = 'select 2;' + + const executedQueries: string[] = [] + addAPIMock({ + method: 'post', + path: '/platform/pg-meta/:ref/query', + response: async ({ request }) => { + const key = new URL(request.url).searchParams.get('key') + if (key !== '') return HttpResponse.json([]) + const { query } = (await request.json()) as { query: string } + executedQueries.push(query) + return HttpResponse.json([]) + }, + }) + + renderQueryTab() + const runButton = await screen.findByRole('button', { name: 'Run' }) + await waitFor(() => expect(runButton).toBeEnabled()) + await userEvent.click(runButton) + + await waitFor(() => expect(executedQueries).toHaveLength(1)) + expect(executedQueries[0]).toContain('select 1') + expect(executedQueries[0]).toContain('select 2') + }) + + it('runs only the selected text from the Run selected menu item', async () => { createDraft({ _tag: 'database' }, 'select 1;\nselect 2;') testContext.selectedText = 'select 2;' @@ -450,9 +477,11 @@ describe('QueryTab execution', () => { }) renderQueryTab() - const runButton = await screen.findByRole('button', { name: 'Run selected' }) + const runButton = await screen.findByRole('button', { name: 'Run' }) await waitFor(() => expect(runButton).toBeEnabled()) - await userEvent.click(runButton) + + await userEvent.click(screen.getByRole('button', { name: 'More actions' })) + await userEvent.click(await screen.findByRole('menuitem', { name: 'Run selected' })) await waitFor(() => expect(executedQueries).toHaveLength(1)) expect(executedQueries[0]).toContain('select 2') @@ -477,7 +506,9 @@ describe('QueryTab execution', () => { }) renderQueryTab() - expect(await screen.findByRole('button', { name: 'Run selected' })).toBeInTheDocument() + await userEvent.click(await screen.findByRole('button', { name: 'More actions' })) + expect(await screen.findByRole('menuitem', { name: 'Run selected' })).toBeEnabled() + await userEvent.keyboard('{Escape}') // Hiding the query panel unmounts CodeEditor entirely. Simulate the selection being // gone by the time it's shown again (a fresh editor instance has no selection yet). @@ -490,9 +521,14 @@ describe('QueryTab execution', () => { expect(showQueryButton).toBeInstanceOf(HTMLButtonElement) await userEvent.click(showQueryButton as HTMLButtonElement) - const runButton = await screen.findByRole('button', { name: 'Run' }) - expect(screen.queryByRole('button', { name: 'Run selected' })).not.toBeInTheDocument() + await userEvent.click(screen.getByRole('button', { name: 'More actions' })) + expect(await screen.findByRole('menuitem', { name: 'Run selected' })).toHaveAttribute( + 'aria-disabled', + 'true' + ) + await userEvent.keyboard('{Escape}') + const runButton = await screen.findByRole('button', { name: 'Run' }) await userEvent.click(runButton) await waitFor(() => expect(executedQueries).toHaveLength(1)) diff --git a/apps/studio/components/ui/AIAssistantPanel/AssistantChatForm.tsx b/apps/studio/components/ui/AIAssistantPanel/AssistantChatForm.tsx index de4cbaf973041..1f8b9edd767d3 100644 --- a/apps/studio/components/ui/AIAssistantPanel/AssistantChatForm.tsx +++ b/apps/studio/components/ui/AIAssistantPanel/AssistantChatForm.tsx @@ -122,7 +122,7 @@ const AssistantChatFormComponent = forwardRef( ref={textAreaRef} disabled={disabled} className={cn( - 'text-base md:text-sm pr-10 pb-9 max-h-64', + 'text-base md:text-sm pr-10 pb-9 max-h-64 rounded-lg', sqlSnippets && sqlSnippets.length > 0 && 'pt-10' )} placeholder={placeholder} diff --git a/packages/ui-patterns/src/Chart/charts/chart-bar.tsx b/packages/ui-patterns/src/Chart/charts/chart-bar.tsx index 7367ca8efb865..49825a3626541 100644 --- a/packages/ui-patterns/src/Chart/charts/chart-bar.tsx +++ b/packages/ui-patterns/src/Chart/charts/chart-bar.tsx @@ -66,6 +66,12 @@ export interface ChartBarProps { showGrid?: boolean showYAxis?: boolean showXAxis?: boolean + XAxisProps?: { + tick?: boolean + tickFormatter?: (value: any) => string + height?: number + [key: string]: any + } YAxisProps?: { tick?: boolean tickFormatter?: (value: any) => string @@ -96,6 +102,7 @@ export const ChartBar = ({ showGrid = false, showYAxis = false, showXAxis = false, + XAxisProps, YAxisProps, }: ChartBarProps) => { const [focusDataIndex, setFocusDataIndex] = useState(null) @@ -132,8 +139,11 @@ export const ChartBar = ({ : false, hide: !showXAxis, interval: 'preserveStartEnd' as const, + tickMargin: showXAxis ? (XAxisProps?.tickMargin ?? 4) : 0, + height: showXAxis ? (XAxisProps?.height ?? 24) : 0, axisLine: { stroke: CHART_COLORS.AXIS }, tickLine: { stroke: CHART_COLORS.AXIS }, + ...XAxisProps, } const yAxisConfig = { diff --git a/packages/ui-patterns/src/Chart/charts/chart-line.tsx b/packages/ui-patterns/src/Chart/charts/chart-line.tsx index a2571ff78c540..c100f8100fde4 100644 --- a/packages/ui-patterns/src/Chart/charts/chart-line.tsx +++ b/packages/ui-patterns/src/Chart/charts/chart-line.tsx @@ -74,6 +74,12 @@ export interface ChartLineProps { showGrid?: boolean showXAxis?: boolean showYAxis?: boolean + XAxisProps?: { + tick?: boolean + tickFormatter?: (value: any) => string + height?: number + [key: string]: any + } YAxisProps?: { tick?: boolean tickFormatter?: (value: any) => string @@ -106,6 +112,7 @@ export const ChartLine = ({ showGrid = false, showXAxis = false, showYAxis = false, + XAxisProps, YAxisProps, strokeWidth = 1.5, referenceLines, @@ -143,8 +150,11 @@ export const ChartLine = ({ : false, hide: !showXAxis, interval: 'preserveStartEnd' as const, + tickMargin: showXAxis ? (XAxisProps?.tickMargin ?? 4) : 0, + height: showXAxis ? (XAxisProps?.height ?? 24) : 0, axisLine: { stroke: CHART_COLORS.AXIS }, tickLine: { stroke: CHART_COLORS.AXIS }, + ...XAxisProps, } const yAxisConfig = { From 442e40a30b032b67286f180ed894364be2913eba Mon Sep 17 00:00:00 2001 From: Danny White <3104761+dnywh@users.noreply.github.com> Date: Thu, 3 Sep 2026 10:09:44 +1000 Subject: [PATCH 2/4] refactor(studio): simplify pipeline advanced settings (#49841) ## What kind of change does this PR introduce? Small Studio UI refactor. ## What is the current behavior? BigQuery-only fields are already conditionally rendered, but repeat that scope in badges. Number inputs also convert non-empty invalid values directly with Number(). ## What is the new behavior? Removes the redundant badges, keeps number fields empty instead of storing NaN, and gives the invalidated-slot menu consistent viewport collision spacing. | Before | After | | --- | --- | | CleanShot 2026-09-02 at 14 40
02@2x | CleanShot 2026-09-02 at 14 40
22@2x | ## To test 1. Open the pipeline creation sheet and expand Advanced settings. 2. Confirm connection pool size and maximum staleness appear only for BigQuery, without BigQuery-only badges. 3. Clear and re-enter the numeric advanced settings. 4. Open Invalidated slot behavior near the viewport edge and confirm the menu remains visible. ## Summary by CodeRabbit * **Bug Fixes** * Improved handling of numeric input in advanced replication settings, including empty and invalid values. * Adjusted the invalidated slot menu positioning for better display. * **UI Improvements** * Simplified labels for connection pool size and maximum staleness settings. --- .../DestinationForm/AdvancedSettings.tsx | 22 +++++-------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationForm/AdvancedSettings.tsx b/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationForm/AdvancedSettings.tsx index 877915b1e54e8..23b147e9699f8 100644 --- a/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationForm/AdvancedSettings.tsx +++ b/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationForm/AdvancedSettings.tsx @@ -5,7 +5,6 @@ import { AccordionContent, AccordionItem, AccordionTrigger, - Badge, FormControl, FormField, FormInputGroupInput, @@ -42,8 +41,8 @@ export const AdvancedSettings = ({ }) => { const handleNumberChange = (field: { onChange: (value?: number) => void }) => (e: ChangeEvent) => { - const val = e.target.value - field.onChange(val === '' ? undefined : Number(val)) + const parsed = e.target.valueAsNumber + field.onChange(e.target.value === '' || Number.isNaN(parsed) ? undefined : parsed) } return ( @@ -59,7 +58,6 @@ export const AdvancedSettings = ({
- {/* Batch wait time - applies to all destinations */} {INVALIDATED_SLOT_BEHAVIOR_LABELS[field.value ?? 'error']} - +

Block startup

@@ -188,12 +186,7 @@ export const AdvancedSettings = ({ name="connectionPoolSize" render={({ field }) => ( - Connection pool size - BigQuery only - - } + label="Connection pool size" layout="horizontal" description="Number of BigQuery connections used for destination writes." > @@ -222,12 +215,7 @@ export const AdvancedSettings = ({ name="maxStalenessMins" render={({ field }) => ( - Maximum staleness - BigQuery only - - } + label="Maximum staleness" layout="horizontal" description="Set the maximum age of query results while BigQuery applies ongoing changes, or leave blank for the freshest results." > From 59af339b8fa7dc9de3910e3e690aa5a4c7e67642 Mon Sep 17 00:00:00 2001 From: Danny White <3104761+dnywh@users.noreply.github.com> Date: Thu, 3 Sep 2026 10:34:14 +1000 Subject: [PATCH 3/4] fix(ui): standardise nav logout label to "Sign out" (#49874) ## What kind of change does this PR introduce? Copy fix across nav dropdowns. ## What is the current behavior? Authenticated nav dropdowns use mixed logout wording: Studio shows "Log out", while www, docs, and learn show "Logout". Studio error flows already use "Sign out". | Before | | --- | | CleanShot 2026-09-02 at 13 07
46@2x | ## What is the new behavior? All four nav dropdowns use **Sign out**, matching the Sign in / Sign up standard and the direction in [DOCS-1328](https://linear.app/supabase/issue/DOCS-1328). Related: [Slack thread](https://supabase.slack.com/archives/C0429V78ACX/p1787081498302919) ## To test Only Studio is possible to test (before merge) given how authentication works across apps on staging: 1. **Studio** (`/dashboard`): open the account avatar dropdown. Confirm the bottom item reads **Sign out**. 2. **www** (`supabase.com`): open the account avatar dropdown. Confirm **Sign out**. 3. **docs** (`supabase.com/docs`): open the account avatar dropdown. Confirm **Sign out**. 4. **learn** (`supabase.com/learn`): open the account avatar dropdown. Confirm **Sign out**. --- .../components/Navigation/NavigationMenu/useDropdownMenu.tsx | 2 +- apps/learn/components/side-navigation.tsx | 2 +- apps/studio/components/interfaces/UserDropdown.tsx | 2 +- apps/www/components/Nav/useDropdownMenu.tsx | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/docs/components/Navigation/NavigationMenu/useDropdownMenu.tsx b/apps/docs/components/Navigation/NavigationMenu/useDropdownMenu.tsx index ececa37033a04..ece8c083cd8bf 100644 --- a/apps/docs/components/Navigation/NavigationMenu/useDropdownMenu.tsx +++ b/apps/docs/components/Navigation/NavigationMenu/useDropdownMenu.tsx @@ -69,7 +69,7 @@ const useDropdownMenu = (user: User | null) => { ], [ { - label: 'Logout', + label: 'Sign out', type: 'button', icon: LogOut, onClick: async () => { diff --git a/apps/learn/components/side-navigation.tsx b/apps/learn/components/side-navigation.tsx index 3668830c1a3e1..de40a76561b3d 100644 --- a/apps/learn/components/side-navigation.tsx +++ b/apps/learn/components/side-navigation.tsx @@ -45,7 +45,7 @@ function SideNavigation({ internalPaths }: SideNavigationProps) { ], [ { - label: 'Logout', + label: 'Sign out', type: 'button', icon: LogOut, onClick: async () => { diff --git a/apps/studio/components/interfaces/UserDropdown.tsx b/apps/studio/components/interfaces/UserDropdown.tsx index 57ba1a63ff008..1aa8466326305 100644 --- a/apps/studio/components/interfaces/UserDropdown.tsx +++ b/apps/studio/components/interfaces/UserDropdown.tsx @@ -200,7 +200,7 @@ export function UserDropdown({ router.push('/logout') }} > - Log out + Sign out diff --git a/apps/www/components/Nav/useDropdownMenu.tsx b/apps/www/components/Nav/useDropdownMenu.tsx index 7378aa483c7ad..81c18b1a462a0 100644 --- a/apps/www/components/Nav/useDropdownMenu.tsx +++ b/apps/www/components/Nav/useDropdownMenu.tsx @@ -44,7 +44,7 @@ const useDropdownMenu = (user: User | null) => { ], [ { - label: 'Logout', + label: 'Sign out', type: 'button', icon: LogOut, onClick: async () => { From 6e8b43d7b4416a79134aeacc2f3c3cf436cf198e Mon Sep 17 00:00:00 2001 From: Gildas Garcia <1122076+djhi@users.noreply.github.com> Date: Thu, 3 Sep 2026 03:36:40 +0200 Subject: [PATCH 4/4] fix: improve `useRefreshOnOpen` hook (#49886) ## Problem The `useRefreshOnOpen` returns a new object each time it runs, making the `useCallback` useless. ## Solution Wrap the function content in `useMemo` instead ## Summary by CodeRabbit * **Refactor** * Improved refresh handling for the replication destination form when it is opened. * Existing refresh behavior remains consistent and reliable. * No user-visible changes to functionality, controls, or configuration options. --- .../DestinationForm/useRefreshOnOpen.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationForm/useRefreshOnOpen.ts b/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationForm/useRefreshOnOpen.ts index 16394d33fa59a..5b02e4ddf81a1 100644 --- a/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationForm/useRefreshOnOpen.ts +++ b/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationForm/useRefreshOnOpen.ts @@ -1,4 +1,4 @@ -import { useCallback } from 'react' +import { useMemo } from 'react' // Replication metadata (publication names, publication tables, source tables, // columns) and similar destination-form option lists follow one rule: @@ -26,12 +26,11 @@ interface UseRefreshOnOpenProps { } export const useRefreshOnOpen = ({ isEnabled = true, refetch }: UseRefreshOnOpenProps) => { - const handleOpenChange = useCallback( - (isOpen: boolean) => { + return useMemo(() => { + const handleOpenChange = (isOpen: boolean) => { if (isOpen && isEnabled) void refetch() - }, - [isEnabled, refetch] - ) + } - return { handleOpenChange } + return { handleOpenChange } + }, [isEnabled, refetch]) }