chore(studio): pull unrelated UI nits out of warehouse prototype

Revert assorted fixes that belong on dnywh/prototype/warehouse-unrelated-fixes
so this branch stays focused on warehouse storage and table details work.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Danny WhiteandCursor committed 2026-06-26 12:12:19 -06:00
1 parent 0807d74e1a
commit 1369459669
11 files changed
+78 -168

No files matched your search

+1 -1
View File
@@ -88,7 +88,7 @@ test.describe.configure({ mode: 'serial' })
3. **`getByText` with exact match** - Good for unique text
```typescript
page.getByText('Data API access', { exact: true })
page.getByText('Data API Access', { exact: true })
```
4. **`locator` with CSS** - Use sparingly, more fragile
@@ -23,7 +23,7 @@ All comments are **advisory**.
3. **`getByText` with exact match** — good for unique text
```typescript
page.getByText('Data API access', { exact: true })
page.getByText('Data API Access', { exact: true })
```
4. **`locator` with CSS** — use sparingly, more fragile
@@ -47,7 +47,7 @@ export const TableReplicationRow = ({
className="px-1.5"
icon={<ExternalLink />}
tooltip={{
content: { side: 'bottom', text: 'Table Editor' },
content: { side: 'bottom', text: 'Open in Table Editor' },
}}
>
<Link
@@ -162,7 +162,6 @@ export const ColumnList = ({
{!isSchemaLocked && isTableEntity && (
<ButtonTooltip
icon={<Plus />}
variant="default"
disabled={!canUpdateColumns}
onClick={() => onAddColumn()}
tooltip={{
@@ -95,9 +95,7 @@ export const ConnectSection = () => {
</span>
<div className="flex min-w-0 flex-1 flex-col gap-1 xl:flex-initial">
<p className="text-sm">{action.heading}</p>
<p className="text-xs text-foreground-lighter leading-none">
{action.subheading}!
</p>
<p className="text-sm text-foreground-lighter">{action.subheading}</p>
</div>
<ChevronRight
size={16}
@@ -1,61 +0,0 @@
import { LOCAL_STORAGE_KEYS } from 'common'
import { useParams } from 'common/hooks'
import { Button } from 'ui'
import { Admonition } from 'ui-patterns'
import { EnableIndexAdvisorButton } from './EnableIndexAdvisorButton'
import { useIndexAdvisorStatus } from '@/components/interfaces/QueryPerformance/hooks/useIsIndexAdvisorStatus'
import { useLocalStorageQuery } from '@/hooks/misc/useLocalStorage'
import { BASE_PATH } from '@/lib/constants'
export const IndexAdvisorNotice = () => {
const { ref } = useParams()
const { isIndexAdvisorAvailable, isIndexAdvisorEnabled } = useIndexAdvisorStatus()
const [isDismissed, setIsDismissed] = useLocalStorageQuery(
LOCAL_STORAGE_KEYS.INDEX_ADVISOR_NOTICE_DISMISSED(ref ?? ''),
false
)
if (!isIndexAdvisorAvailable || isIndexAdvisorEnabled || isDismissed) return null
return (
<div>
<Admonition showIcon={false} type="tip" className="relative overflow-hidden mb-4">
<div className="absolute -inset-16 z-0 opacity-50">
<img
src={`${BASE_PATH}/img/reports/bg-grafana-dark.svg`}
alt="Index Advisor"
className="w-full h-full object-cover object-right hidden dark:block"
/>
<img
src={`${BASE_PATH}/img/reports/bg-grafana-light.svg`}
alt="Index Advisor"
className="w-full h-full object-cover object-right dark:hidden"
/>
<div className="absolute inset-0 bg-linear-to-r from-background-alternative to-transparent" />
</div>
<div className="relative z-10 flex flex-col md:flex-row md:items-center gap-y-2 md:gap-x-8 justify-between px-2 py-1">
<div className="flex flex-col gap-y-0.5">
<div className="flex flex-col gap-y-2 items-start">
<p className="text-sm font-medium">Enable Index Advisor</p>
</div>
<p className="text-sm text-foreground-lighter text-balance">
Recommends indexes to improve query performance.
</p>
</div>
<div className="flex items-center gap-x-2">
<Button
variant="default"
size="tiny"
onClick={() => setIsDismissed(true)}
aria-label="Dismiss notification"
>
Dismiss
</Button>
<EnableIndexAdvisorButton />
</div>
</div>
</Admonition>
</div>
)
}
@@ -1,16 +1,16 @@
import Editor, { Monaco, OnMount } from '@monaco-editor/react'
import { Monaco, OnMount } from '@monaco-editor/react'
import { useDebounce } from '@uidotdev/usehooks'
import { LOCAL_STORAGE_KEYS, useParams } from 'common'
import { noop } from 'lodash'
import { useRouter } from 'next/router'
import { MutableRefObject, useEffect, useRef, useState } from 'react'
import { cn } from 'ui'
import { RefObject, useEffect, useRef, useState } from 'react'
import { Admonition } from 'ui-patterns'
import { useSetCommandMenuOpen } from 'ui-patterns/CommandMenu'
import type { IStandaloneCodeEditor } from './SQLEditor.types'
import { createSqlSnippetSkeletonV2 } from './SQLEditor.utils'
import { SIDEBAR_KEYS } from '@/components/layouts/ProjectLayout/LayoutSidebar/LayoutSidebarProvider'
import { getEditorSelectionParts } from '@/components/ui/AIEditor/utils'
import { CodeEditor } from '@/components/ui/CodeEditor/CodeEditor'
import { useLocalStorageQuery } from '@/hooks/misc/useLocalStorage'
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
import { useProfile } from '@/lib/profile'
@@ -19,14 +19,17 @@ import { SHORTCUT_IDS } from '@/state/shortcuts/registry'
import { useIsShortcutEnabled } from '@/state/shortcuts/useIsShortcutEnabled'
import { useSidebarManagerSnapshot } from '@/state/sidebar-manager-state'
import { useSqlEditorV2StateSnapshot } from '@/state/sql-editor-v2'
import { wasNeverPersisted } from '@/state/sql-editor/sql-editor-lifecycle'
import { canEditSnippet } from '@/state/sql-editor/sql-editor-rules'
import { useSqlEditorSaveCoordinator } from '@/state/sql-editor/sql-editor-save-coordinator'
import { useTabsStateSnapshot } from '@/state/tabs'
export type MonacoEditorProps = {
id: string
snippetName: string
className?: string
editorRef: MutableRefObject<IStandaloneCodeEditor | null>
monacoRef: MutableRefObject<Monaco | null>
editorRef: RefObject<IStandaloneCodeEditor | null>
monacoRef: RefObject<Monaco | null>
autoFocus?: boolean
executeQuery: () => void
executeExplainQuery: () => void
@@ -44,7 +47,7 @@ export type MonacoEditorProps = {
placeholder?: string
}
const MonacoEditor = ({
export const MonacoEditor = ({
id,
snippetName,
editorRef,
@@ -74,20 +77,12 @@ const MonacoEditor = ({
LOCAL_STORAGE_KEYS.SQL_EDITOR_INTELLISENSE,
true
)
const isAIAssistantHotkeyEnabled = useIsShortcutEnabled(SHORTCUT_IDS.AI_ASSISTANT_TOGGLE)
const isCommandMenuHotkeyEnabled = useIsShortcutEnabled(SHORTCUT_IDS.COMMAND_MENU_OPEN)
const setCommandMenuOpen = useSetCommandMenuOpen()
// [Joshen] Lodash debounce doesn't seem to be working here, so opting to use useDebounce
const [value, setValue] = useState('')
const debouncedValue = useDebounce(value, 1000)
const snippet = snapV2.snippets[id]
const disableEdit =
snippet?.snippet.visibility === 'project' && snippet?.snippet.owner_id !== profile?.id
const executeQueryRef = useRef(executeQuery)
executeQueryRef.current = executeQuery
const disableEdit = !!snippet && !canEditSnippet(snippet.snippet, profile?.id)
const executeExplainQueryRef = useRef(executeExplainQuery)
executeExplainQueryRef.current = executeExplainQuery
@@ -95,22 +90,18 @@ const MonacoEditor = ({
const prettifyQueryRef = useRef(prettifyQuery)
prettifyQueryRef.current = prettifyQuery
const isAIAssistantHotkeyEnabled = useIsShortcutEnabled(SHORTCUT_IDS.AI_ASSISTANT_TOGGLE)
const aiHotkeyEnabledRef = useRef(isAIAssistantHotkeyEnabled)
aiHotkeyEnabledRef.current = isAIAssistantHotkeyEnabled
const commandMenuHotkeyEnabledRef = useRef(isCommandMenuHotkeyEnabled)
commandMenuHotkeyEnabledRef.current = isCommandMenuHotkeyEnabled
const { requestSave } = useSqlEditorSaveCoordinator()
const requestSaveRef = useRef(requestSave)
requestSaveRef.current = requestSave
const setCommandMenuOpenRef = useRef(setCommandMenuOpen)
setCommandMenuOpenRef.current = setCommandMenuOpen
const handleEditorOnMount: OnMount = async (editor, monaco) => {
editorRef.current = editor
monacoRef.current = monaco
const model = editorRef.current.getModel()
const handleEditorOnMount: OnMount = (editor, monaco) => {
const model = editor.getModel()
if (model !== null) {
monacoRef.current.editor.setModelMarkers(model, 'owner', [])
monaco.editor.setModelMarkers(model, 'owner', [])
}
// Blur the editor on Escape so users can hop out to the rest of the UI.
@@ -136,17 +127,6 @@ const MonacoEditor = ({
].join(' && ')
)
editor.addAction({
id: 'run-query',
label: 'Run Query',
keybindings: [monaco.KeyMod.CtrlCmd + monaco.KeyCode.Enter],
contextMenuGroupId: 'operation',
contextMenuOrder: 0,
run: () => {
executeQueryRef.current()
},
})
if (showExplainAction) {
editor.addAction({
id: 'run-explain-query',
@@ -167,7 +147,7 @@ const MonacoEditor = ({
contextMenuGroupId: 'operation',
contextMenuOrder: 0,
run: () => {
if (snippet) snapV2.addNeedsSaving(snippet.snippet.id)
if (snippet) requestSaveRef.current(snippet.snippet.id)
},
})
@@ -188,13 +168,15 @@ const MonacoEditor = ({
contextMenuGroupId: 'operation',
contextMenuOrder: 1,
run: () => {
const selectedValue = (editorRef?.current as any)
.getModel()
.getValueInRange((editorRef?.current as any)?.getSelection())
const selection = editorRef?.current?.getSelection()
if (!selection) return
const selectedValue = editorRef?.current?.getModel()?.getValueInRange(selection)
openSidebar(SIDEBAR_KEYS.AI_ASSISTANT)
aiSnap.newChat({
name: 'Explain code section',
sqlSnippets: [selectedValue],
sqlSnippets: [selectedValue ?? ''],
initialInput: 'Can you explain this section to me in more detail?',
})
},
@@ -223,15 +205,6 @@ const MonacoEditor = ({
})
}
// Monaco claims Cmd+K as a chord prefix, which swallows the global command
// menu shortcut while the editor is focused. Intercept it here and open the
// command menu directly so it works the same inside and outside the editor.
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyK, () => {
if (commandMenuHotkeyEnabledRef.current) {
setCommandMenuOpenRef.current(true)
}
})
editor.onDidChangeCursorSelection(({ selection }) => {
const noSelection =
selection.startLineNumber === selection.endLineNumber &&
@@ -239,11 +212,6 @@ const MonacoEditor = ({
onHasSelection(!noSelection)
})
if (autoFocus) {
if (editor.getValue().length === 1) editor.setPosition({ lineNumber: 1, column: 2 })
editor.focus()
}
onMount?.(editor)
}
@@ -263,7 +231,7 @@ const MonacoEditor = ({
// than push. The caller navigated to `/sql/new?content=...` (a long,
// one-shot URL); replacing collapses it out of history so Back returns to
// the originating page instead of a wasted step that re-seeds the snippet.
if (router.query.content) {
if (router.query.content !== undefined) {
router.replace(`/project/${ref}/sql/${snippet.id}`, undefined, { shallow: true })
} else {
router.push(`/project/${ref}/sql/${snippet.id}`, undefined, { shallow: true })
@@ -275,7 +243,7 @@ const MonacoEditor = ({
useEffect(() => {
if (debouncedValue.length > 0 && snippet) {
const shouldInvalidate = snippet.snippet.isNotSavedInDatabaseYet
const shouldInvalidate = wasNeverPersisted(snippet.snippet.status)
snapV2.setSql({ id, sql: value, shouldInvalidate })
}
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -297,29 +265,26 @@ const MonacoEditor = ({
description="This snippet has been shared to the project and is only editable by the owner who created this snippet. You may duplicate this snippet into a personal copy by right clicking on the snippet and selecting “Duplicate query”."
/>
)}
<Editor
className={cn(className, 'monaco-editor')}
theme={'supabase'}
onMount={handleEditorOnMount}
onChange={handleEditorChange}
defaultLanguage="pgsql"
<CodeEditor
id={id}
language="pgsql"
className={className}
autofocus={autoFocus}
isReadOnly={disableEdit}
defaultValue={snippet?.snippet.content?.unchecked_sql}
path={id}
editorRef={editorRef}
monacoRef={monacoRef}
actions={{
runQuery: { enabled: true, callback: executeQuery },
formatDocument: { enabled: false, callback: noop },
placeholderFill: { enabled: false },
}}
options={{
tabSize: 2,
fontSize: 13,
placeholder,
lineDecorationsWidth: 0,
readOnly: disableEdit,
minimap: { enabled: false },
wordWrap: 'on',
padding: { top: 4 },
// [Joshen] Commenting the following out as it causes the autocomplete suggestion popover
// to be positioned wrongly somehow. I'm not sure if this affects anything though, but leaving
// comment just in case anyone might be wondering. Relevant issues:
// - https://github.com/microsoft/monaco-editor/issues/2229
// - https://github.com/microsoft/monaco-editor/issues/2503
// fixedOverflowWidgets: true,
fixedOverflowWidgets: false,
lineNumbersMinChars: 5,
scrollBeyondLastLine: true,
suggest: {
showMethods: intellisenseEnabled,
showFunctions: intellisenseEnabled,
@@ -351,9 +316,9 @@ const MonacoEditor = ({
showSnippets: intellisenseEnabled,
},
}}
onInputChange={handleEditorChange}
onMount={handleEditorOnMount}
/>
</>
)
}
export default MonacoEditor
@@ -322,7 +322,7 @@ export const ApiAccessToggle = ({
<div className="space-y-3">
<div className="flex items-center justify-between gap-3">
<div>
{!hideHeading && <h5>Data API access</h5>}
{!hideHeading && <h5>Data API Access</h5>}
<p className="text-sm text-foreground-lighter">
Allow this table to be queried via Supabase client libraries or the API directly
</p>
@@ -5,6 +5,8 @@ import { useMemo, useState } from 'react'
import { toast } from 'sonner'
import {
Button,
Card,
CardContent,
Dialog,
DialogContent,
DialogDescription,
@@ -15,7 +17,6 @@ import {
DialogTitle,
DialogTrigger,
} from 'ui'
import { Admonition } from 'ui-patterns/admonition'
import { CodeBlock } from 'ui-patterns/CodeBlock'
import { AUTO_ENABLE_RLS_EVENT_TRIGGER_SQL } from '@/components/interfaces/Database/Triggers/EventTriggersList/EventTriggers.constants'
@@ -59,13 +60,21 @@ export const AutoEnableRLSNotice = ({ iconOnly }: { iconOnly?: boolean }) => {
}
return (
<Admonition
type="note"
layout="horizontal"
title="Auto-enable RLS for new tables"
description="We recommend creating an event trigger that enables Row Level Security on all new tables."
actions={
<>
<Card>
<CardContent className="flex items-center justify-between">
<div className="flex items-center gap-x-4">
<div className="rounded-lg bg-surface-300 text-foreground-light w-10 h-10 flex items-center justify-center">
<ShieldCheck size={18} />
</div>
<div className="text-sm">
<p>Auto-enable RLS for new tables</p>
<p className="text-foreground-lighter">
Create an event trigger that enables Row Level Security on all new tables
</p>
</div>
</div>
<div className="flex items-center gap-x-2">
<CreateEnsureRLSTriggerDialog />
<ButtonTooltip
icon={<X />}
@@ -74,9 +83,9 @@ export const AutoEnableRLSNotice = ({ iconOnly }: { iconOnly?: boolean }) => {
tooltip={{ content: { side: 'bottom', text: 'Minimize' } }}
onClick={() => setIsMinimized(true)}
/>
</>
}
/>
</div>
</CardContent>
</Card>
)
}
@@ -122,7 +131,7 @@ const CreateEnsureRLSTriggerDialog = ({ iconOnly }: { iconOnly?: boolean }) => {
tooltip={{ content: { side: 'bottom', text: 'Auto-enable RLS for new tables' } }}
/>
) : (
<Button variant="default">Learn more</Button>
<Button variant="primary">Learn more</Button>
)}
</DialogTrigger>
<DialogContent size="large">
@@ -57,14 +57,14 @@ async function verifyTablePrivileges(
}
/**
* Locates the API access toggle switch for Data API access.
* Locates the API access toggle switch for Data API Access.
* Only present when creating or duplicating a table (not when editing).
*/
function getApiAccessSwitch(page: Page) {
const sidePanel = page.getByTestId('table-editor-side-panel')
const dataApiSection = sidePanel
.locator('div')
.filter({ hasText: 'Data API access' })
.filter({ hasText: 'Data API Access' })
.filter({ has: page.getByRole('switch') })
return dataApiSection.getByRole('switch')
}
@@ -239,10 +239,10 @@ test.describe('API Access Toggle', () => {
await page.getByRole('menuitem', { name: 'Edit table' }).click()
await expect(page.getByTestId('table-editor-side-panel')).toBeVisible()
// Data API access section is visible
// Data API Access section is visible
await expect(
page.getByText('Data API access'),
'Data API access label should be visible in edit mode'
page.getByText('Data API Access'),
'Data API Access label should be visible in edit mode'
).toBeVisible()
// In edit mode the panel shows a "Manage access" link instead of a toggle switch
@@ -32,14 +32,14 @@ const DropdownMenuSubTrigger = React.forwardRef<
<DropdownMenuPrimitive.SubTrigger
ref={ref}
className={cn(
'flex cursor-default select-none items-center rounded-xs pl-2 pr-1 py-1.5 text-xs outline-hidden focus:bg-overlay-hover data-[state=open]:bg-overlay-hover',
'flex cursor-default select-none items-center rounded-xs px-2 py-1.5 text-xs outline-hidden focus:bg-overlay-hover data-[state=open]:bg-overlay-hover',
inset && 'pl-8',
className
)}
{...props}
>
{children}
<ChevronRight className="h-4 w-4 ml-auto!" strokeWidth={1.5} />
<ChevronRight className="h-4 w-4 ml-auto!" />
</DropdownMenuPrimitive.SubTrigger>
))
DropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName