diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index cb06d2c3907..6fe68cc8e93 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -33,6 +33,8 @@ pnpm typecheck # typecheck all packages **Language** — Use U.S. English everywhere. +**Studio shortcuts** — when adding or changing repeated Studio UI actions, use the shared shortcut registry and primitives in `apps/studio/state/shortcuts/` and `apps/studio/components/ui/Shortcut*.tsx`. Prefer registered, discoverable shortcuts over one-off keyboard listeners; keep `G then ...` chords for navigation. + ## Studio Pages router. Co-locate sub-components with parent. Avoid barrel re-export files. diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 75e6f46f14c..d9819a0edf8 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -55,5 +55,6 @@ Path-specific rules in `.github/instructions/`: - **E2E Tests**: `studio-e2e-tests.instructions.md` — selector priority, anti-patterns (`waitForTimeout`, `force: true`) - **Composition Patterns**: `studio-composition-patterns.instructions.md` — avoid boolean props, use compound components - **shadcn/Radix Components**: `studio-shadcn-components.instructions.md` — accessibility handled by primitives, do not flag +- **Keyboard Shortcuts**: `studio-shortcuts.instructions.md` — shared registry usage, discovery, collision checks These files are scoped to `apps/studio/` and applied automatically during reviews. diff --git a/.github/instructions/studio-shortcuts.instructions.md b/.github/instructions/studio-shortcuts.instructions.md new file mode 100644 index 00000000000..c11944993c5 --- /dev/null +++ b/.github/instructions/studio-shortcuts.instructions.md @@ -0,0 +1,37 @@ +--- +applyTo: 'apps/studio/**' +--- + +# Studio Shortcut Review Rules + +All comments are **advisory**. + +## Core Principle + +When Studio UI changes introduce or materially alter repeated user actions, consider whether keyboard shortcut coverage should be added or updated. Shortcuts should use the shared Studio shortcut system and be discoverable from the visible UI. + +## When to Flag + +- PR adds a primary repeated action, toolbar action, list/table operation, or sub-page navigation without considering shortcut coverage. +- PR adds a one-off `keydown` listener for a normal Studio action instead of using the shortcut registry and `useShortcut`. +- PR registers a shortcut but does not expose it via `ShortcutTooltip`, `ShortcutBadge`, or command-menu badge where the action is visible. +- PR wires `useShortcut` and `ShortcutTooltip` separately for a single visible element instead of using the `` wrapper. +- PR uses `G then ...` for a non-navigation action. +- PR adds a broad `Mod+letter` shortcut that overlaps common browser, editor, system, copy/save/search, or devtools behavior. +- PR adds a shortcut without checking existing registry and non-registry listeners for collisions. +- PR adds a search or filter input with custom Escape handling instead of `onSearchInputEscape` from `@/lib/keyboard`. + +## Preferred Pattern + +- Add definitions in `apps/studio/state/shortcuts/registry.ts` or `apps/studio/state/shortcuts/registry/*`. +- Add or reuse a cheatsheet group in `apps/studio/state/shortcuts/referenceGroups.ts` when a shortcut belongs to a new surface; prefer existing groups for global actions, navigation, and established feature surfaces. +- Register with `useShortcut`. +- For a single visible element that owns the action (button, icon button, menu trigger), prefer the `` wrapper in `apps/studio/components/ui/Shortcut.tsx` — it binds `useShortcut` and `ShortcutTooltip` from one `id` so the hotkey and tooltip can't drift. Drop down to `useShortcut` + `ShortcutTooltip`/`ShortcutBadge` separately only when the trigger and the visible affordance live on different elements. +- Gate availability with `enabled`. +- Use `showInSettings: false` for contextual shortcuts that only work inside a page state, panel, sheet, or selected-row mode. +- Surface visible actions with `ShortcutTooltip` or `ShortcutBadge`. +- For sheet-owned actions, mount the shortcut from the sheet or a sheet-owned hook; gate with `enabled` when the action only applies while the sheet is open. See `apps/studio/components/interfaces/ConnectSheet/useConnectSheetShortcut.ts`. +- For search/filter inputs, wire `onKeyDown` to `onSearchInputEscape(value, setValue)` from `@/lib/keyboard` so Escape clears the value, then blurs on a second press, and doesn't bubble to a parent dialog/popover. Don't re-implement this with a local `keydown` listener. +- Prefer scoped, mnemonic sequential chords over global modifier chords. + +Canonical implementation context: `apps/studio/state/shortcuts/registry.ts`, `apps/studio/state/shortcuts/useShortcut.tsx`, and `apps/studio/components/ui/Shortcut*.tsx` diff --git a/apps/studio/components/interfaces/ConnectButton/Connect.Commands.test.tsx b/apps/studio/components/interfaces/ConnectButton/Connect.Commands.test.tsx new file mode 100644 index 00000000000..057f5aa29dc --- /dev/null +++ b/apps/studio/components/interfaces/ConnectButton/Connect.Commands.test.tsx @@ -0,0 +1,70 @@ +import { renderHook } from '@testing-library/react' +import { beforeEach, describe, expect, it, vi } from 'vitest' + +import { useConnectCommands } from './Connect.Commands' +import { PROJECT_STATUS } from '@/lib/constants' + +const { + mockSetCommandMenuOpen, + mockSetConnectTab, + mockSetShowConnect, + mockUseRegisterCommands, + mockUseSelectedProjectQuery, +} = vi.hoisted(() => ({ + mockSetCommandMenuOpen: vi.fn(), + mockSetConnectTab: vi.fn(), + mockSetShowConnect: vi.fn(), + mockUseRegisterCommands: vi.fn(), + mockUseSelectedProjectQuery: vi.fn(), +})) + +vi.mock('ui-patterns/CommandMenu', () => ({ + useRegisterCommands: mockUseRegisterCommands, + useSetCommandMenuOpen: () => mockSetCommandMenuOpen, +})) + +vi.mock('nuqs', () => ({ + parseAsBoolean: { withDefault: vi.fn(() => 'showConnectParser') }, + parseAsString: 'stringParser', + useQueryState: (key: string) => { + if (key === 'showConnect') return [false, mockSetShowConnect] + if (key === 'connectTab') return [null, mockSetConnectTab] + return [null, vi.fn()] + }, +})) + +vi.mock('@/hooks/misc/useSelectedProject', () => ({ + useSelectedProjectQuery: mockUseSelectedProjectQuery, +})) + +describe('useConnectCommands', () => { + beforeEach(() => { + vi.clearAllMocks() + mockUseSelectedProjectQuery.mockReturnValue({ + data: { status: PROJECT_STATUS.ACTIVE_HEALTHY }, + }) + }) + + it('shows the shortcut badge on the Connect command', () => { + renderHook(() => useConnectCommands()) + + const [, commands, options] = mockUseRegisterCommands.mock.calls[0] + + expect(options.enabled).toBe(true) + expect(commands[0]).toMatchObject({ + id: 'connect-to-project', + name: 'Connect to your project', + }) + expect(commands[0].badge).toBeTypeOf('function') + }) + + it('disables the commands when the selected project cannot connect', () => { + mockUseSelectedProjectQuery.mockReturnValue({ + data: { status: PROJECT_STATUS.INACTIVE }, + }) + + renderHook(() => useConnectCommands()) + + expect(mockUseRegisterCommands.mock.calls[0][2].enabled).toBe(false) + }) +}) diff --git a/apps/studio/components/interfaces/ConnectButton/Connect.Commands.tsx b/apps/studio/components/interfaces/ConnectButton/Connect.Commands.tsx index 2364a4a2de4..96949e35c35 100644 --- a/apps/studio/components/interfaces/ConnectButton/Connect.Commands.tsx +++ b/apps/studio/components/interfaces/ConnectButton/Connect.Commands.tsx @@ -1,5 +1,7 @@ import { Plug } from 'lucide-react' import { parseAsBoolean, parseAsString, useQueryState } from 'nuqs' +import { Fragment } from 'react' +import { KeyboardShortcut } from 'ui' import type { ICommand } from 'ui-patterns/CommandMenu' import { useRegisterCommands, useSetCommandMenuOpen } from 'ui-patterns/CommandMenu' @@ -7,11 +9,25 @@ import { COMMAND_MENU_SECTIONS } from '@/components/interfaces/App/CommandMenu/C import { orderCommandSectionsByPriority } from '@/components/interfaces/App/CommandMenu/ordering' import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' import { PROJECT_STATUS } from '@/lib/constants' +import { hotkeyToKeys } from '@/state/shortcuts/formatShortcut' +import { SHORTCUT_DEFINITIONS, SHORTCUT_IDS } from '@/state/shortcuts/registry' + +const ConnectShortcutBadge = () => ( +
+ {SHORTCUT_DEFINITIONS[SHORTCUT_IDS.CONNECT_OPEN_SHEET].sequence.map((step, index) => ( + + {index > 0 && then} + + + ))} +
+) export function useConnectCommands() { const setIsOpen = useSetCommandMenuOpen() const { data: selectedProject } = useSelectedProjectQuery() const isActiveHealthy = selectedProject?.status === PROJECT_STATUS.ACTIVE_HEALTHY + const enabled = !!selectedProject && isActiveHealthy const [, setShowConnect] = useQueryState('showConnect', parseAsBoolean.withDefault(false)) const [, setConnectTab] = useQueryState('connectTab', parseAsString) @@ -27,6 +43,7 @@ export function useConnectCommands() { setIsOpen(false) }, icon: () => , + badge: ConnectShortcutBadge, }, { id: 'connect-mcp', @@ -40,7 +57,7 @@ export function useConnectCommands() { }, ] as ICommand[], { - enabled: !!selectedProject && isActiveHealthy, + enabled, orderSection: orderCommandSectionsByPriority, sectionMeta: { priority: 2 }, } diff --git a/apps/studio/components/interfaces/ConnectButton/ConnectButton.tsx b/apps/studio/components/interfaces/ConnectButton/ConnectButton.tsx index 9d6be5770f6..9068c57587c 100644 --- a/apps/studio/components/interfaces/ConnectButton/ConnectButton.tsx +++ b/apps/studio/components/interfaces/ConnectButton/ConnectButton.tsx @@ -4,10 +4,12 @@ import { ComponentProps } from 'react' import { Button, cn } from 'ui' import { ButtonTooltip } from '@/components/ui/ButtonTooltip' +import { ShortcutTooltip } from '@/components/ui/ShortcutTooltip' import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' import { PROJECT_STATUS } from '@/lib/constants' import { useTrack } from '@/lib/telemetry/track' import { useAppStateSnapshot } from '@/state/app-state' +import { SHORTCUT_IDS } from '@/state/shortcuts/registry' interface ConnectButtonProps { buttonType?: ComponentProps['type'] @@ -25,12 +27,39 @@ export const ConnectButton = ({ const isActiveHealthy = selectedProject?.status === PROJECT_STATUS.ACTIVE_HEALTHY const track = useTrack() - const [, setShowConnect] = useQueryState('showConnect', parseAsBoolean.withDefault(false)) + const [showConnect, setShowConnect] = useQueryState( + 'showConnect', + parseAsBoolean.withDefault(false) + ) + + if (isActiveHealthy) { + return ( + + + + ) + } return ( } onClick={() => { @@ -41,9 +70,7 @@ export const ConnectButton = ({ tooltip={{ content: { side: 'bottom', - text: !isActiveHealthy - ? 'Project is currently not active and cannot be connected' - : undefined, + text: 'Project is currently not active and cannot be connected', }, }} > diff --git a/apps/studio/components/interfaces/ConnectSheet/ConnectSheet.tsx b/apps/studio/components/interfaces/ConnectSheet/ConnectSheet.tsx index d01013598f1..2db9e13fd30 100644 --- a/apps/studio/components/interfaces/ConnectSheet/ConnectSheet.tsx +++ b/apps/studio/components/interfaces/ConnectSheet/ConnectSheet.tsx @@ -9,6 +9,7 @@ import { CONNECT_MODES } from './Connect.types' import { ConnectConfigSection, ModeSelector } from './ConnectConfigSection' import { ConnectStepsSection } from './ConnectStepsSection' import { useAvailableConnectModes } from './useAvailableConnectModes' +import { useConnectSheetShortcut } from './useConnectSheetShortcut' import { useConnectState } from './useConnectState' import { getKeys, useAPIKeysQuery } from '@/data/api-keys/api-keys-query' import { useProjectApiUrl } from '@/data/config/project-endpoint-query' @@ -36,6 +37,8 @@ function mapConnectTabToMode(tab: string | null): ConnectMode | null { export const ConnectSheet = () => { const { ref: projectRef } = useParams() + useConnectSheetShortcut() + const availableModeIds = useAvailableConnectModes() const [showConnect, setShowConnect] = useQueryState( diff --git a/apps/studio/components/interfaces/ConnectSheet/useConnectSheetShortcut.test.ts b/apps/studio/components/interfaces/ConnectSheet/useConnectSheetShortcut.test.ts new file mode 100644 index 00000000000..464ec1b252c --- /dev/null +++ b/apps/studio/components/interfaces/ConnectSheet/useConnectSheetShortcut.test.ts @@ -0,0 +1,85 @@ +import { renderHook } from '@testing-library/react' +import { beforeEach, describe, expect, it, vi } from 'vitest' + +import { useConnectSheetShortcut } from './useConnectSheetShortcut' +import { PROJECT_STATUS } from '@/lib/constants' +import { SHORTCUT_IDS } from '@/state/shortcuts/registry' + +const { + mockSetCommandMenuOpen, + mockSetConnectSheetSource, + mockSetShowConnect, + mockUseSelectedProjectQuery, + mockUseShortcut, +} = vi.hoisted(() => ({ + mockSetCommandMenuOpen: vi.fn(), + mockSetConnectSheetSource: vi.fn(), + mockSetShowConnect: vi.fn(), + mockUseSelectedProjectQuery: vi.fn(), + mockUseShortcut: vi.fn(), +})) + +vi.mock('ui-patterns/CommandMenu', () => ({ + useSetCommandMenuOpen: () => mockSetCommandMenuOpen, +})) + +vi.mock('nuqs', () => ({ + parseAsBoolean: { withDefault: vi.fn(() => 'showConnectParser') }, + useQueryState: () => [false, mockSetShowConnect], +})) + +vi.mock('@/hooks/misc/useSelectedProject', () => ({ + useSelectedProjectQuery: mockUseSelectedProjectQuery, +})) + +vi.mock('@/state/app-state', () => ({ + useAppStateSnapshot: () => ({ setConnectSheetSource: mockSetConnectSheetSource }), +})) + +vi.mock('@/state/shortcuts/useShortcut', () => ({ + useShortcut: mockUseShortcut, +})) + +describe('useConnectSheetShortcut', () => { + beforeEach(() => { + vi.clearAllMocks() + mockUseSelectedProjectQuery.mockReturnValue({ + data: { status: PROJECT_STATUS.ACTIVE_HEALTHY }, + }) + }) + + it('registers the Connect sheet shortcut when the selected project can connect', () => { + renderHook(() => useConnectSheetShortcut()) + + expect(mockUseShortcut).toHaveBeenCalledWith( + SHORTCUT_IDS.CONNECT_OPEN_SHEET, + expect.any(Function), + { enabled: true } + ) + }) + + it('opens the Connect sheet from the keyboard shortcut', () => { + renderHook(() => useConnectSheetShortcut()) + + const shortcutCallback = mockUseShortcut.mock.calls[0][1] + shortcutCallback() + + expect(mockSetConnectSheetSource).toHaveBeenCalledWith('keyboard_shortcut') + expect(mockSetCommandMenuOpen).toHaveBeenCalledWith(false) + expect(mockSetShowConnect).toHaveBeenCalledWith(true) + }) + + it('disables the shortcut when the selected project cannot connect', () => { + mockUseSelectedProjectQuery.mockReturnValue({ + data: { status: PROJECT_STATUS.INACTIVE }, + }) + + renderHook(() => useConnectSheetShortcut()) + + expect(mockUseShortcut).toHaveBeenCalledWith( + SHORTCUT_IDS.CONNECT_OPEN_SHEET, + expect.any(Function), + { enabled: false } + ) + }) +}) diff --git a/apps/studio/components/interfaces/ConnectSheet/useConnectSheetShortcut.ts b/apps/studio/components/interfaces/ConnectSheet/useConnectSheetShortcut.ts new file mode 100644 index 00000000000..ed8d1bee0b3 --- /dev/null +++ b/apps/studio/components/interfaces/ConnectSheet/useConnectSheetShortcut.ts @@ -0,0 +1,26 @@ +import { parseAsBoolean, useQueryState } from 'nuqs' +import { useSetCommandMenuOpen } from 'ui-patterns/CommandMenu' + +import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' +import { PROJECT_STATUS } from '@/lib/constants' +import { useAppStateSnapshot } from '@/state/app-state' +import { SHORTCUT_IDS } from '@/state/shortcuts/registry' +import { useShortcut } from '@/state/shortcuts/useShortcut' + +export function useConnectSheetShortcut() { + const setCommandMenuOpen = useSetCommandMenuOpen() + const { data: selectedProject } = useSelectedProjectQuery() + const { setConnectSheetSource } = useAppStateSnapshot() + const [, setShowConnect] = useQueryState('showConnect', parseAsBoolean.withDefault(false)) + const enabled = selectedProject?.status === PROJECT_STATUS.ACTIVE_HEALTHY + + useShortcut( + SHORTCUT_IDS.CONNECT_OPEN_SHEET, + () => { + setConnectSheetSource('keyboard_shortcut') + setCommandMenuOpen(false) + setShowConnect(true) + }, + { enabled } + ) +} diff --git a/apps/studio/components/ui/GlobalShortcuts/ShortcutsReferenceSheet.test.tsx b/apps/studio/components/ui/GlobalShortcuts/ShortcutsReferenceSheet.test.tsx index c44f85a3c50..5960c852d8e 100644 --- a/apps/studio/components/ui/GlobalShortcuts/ShortcutsReferenceSheet.test.tsx +++ b/apps/studio/components/ui/GlobalShortcuts/ShortcutsReferenceSheet.test.tsx @@ -32,6 +32,13 @@ const ACTIVE_DATABASE_SHORTCUT_IDS = [ SHORTCUT_IDS.NAV_DATABASE_TABLES, ] satisfies ShortcutId[] +const ACTIVE_GLOBAL_ACTION_SHORTCUT_IDS = [ + ...ACTIVE_SHORTCUT_IDS, + SHORTCUT_IDS.AI_ASSISTANT_TOGGLE, + SHORTCUT_IDS.INLINE_EDITOR_TOGGLE, + SHORTCUT_IDS.CONNECT_OPEN_SHEET, +] satisfies ShortcutId[] + const ACTIVE_AUTH_SHORTCUT_IDS = [ ...ACTIVE_SHORTCUT_IDS, SHORTCUT_IDS.NAV_AUTH_USERS, @@ -163,6 +170,20 @@ describe('ShortcutsReferenceSheet', () => { expect(screen.getByText('Go to Tables')).toBeInTheDocument() }) + it('shows the global actions section when global action shortcuts are active', async () => { + renderShortcutsReferenceSheet(ACTIVE_GLOBAL_ACTION_SHORTCUT_IDS) + + expect(await screen.findByText('Global Actions')).toBeInTheDocument() + expect(screen.getByText('Toggle AI Assistant panel')).toBeInTheDocument() + expect(screen.getByText('Toggle inline SQL editor')).toBeInTheDocument() + expect(screen.getByText('Open Connect sheet')).toBeInTheDocument() + expect(screen.getByText('O')).toBeInTheDocument() + expect(screen.getAllByText('then').length).toBeGreaterThan(0) + expect(screen.getByText('C')).toBeInTheDocument() + expect(screen.queryByText('AI Assistant')).not.toBeInTheDocument() + expect(screen.queryByText('Inline Editor')).not.toBeInTheDocument() + }) + it('shows the auth navigation section when auth shortcuts are active', async () => { renderShortcutsReferenceSheet(ACTIVE_AUTH_SHORTCUT_IDS) diff --git a/apps/studio/state/app-state.ts b/apps/studio/state/app-state.ts index 82740a3e25c..27cea14b872 100644 --- a/apps/studio/state/app-state.ts +++ b/apps/studio/state/app-state.ts @@ -70,8 +70,8 @@ export const appState = proxy({ appState.mobileMenuOpen = value }, - connectSheetSource: 'header_button' as 'header_button' | 'connect_section', - setConnectSheetSource: (value: 'header_button' | 'connect_section') => { + connectSheetSource: 'header_button' as 'header_button' | 'connect_section' | 'keyboard_shortcut', + setConnectSheetSource: (value: 'header_button' | 'connect_section' | 'keyboard_shortcut') => { appState.connectSheetSource = value }, diff --git a/apps/studio/state/shortcuts/referenceGroups.ts b/apps/studio/state/shortcuts/referenceGroups.ts index a9bb1461b6f..14b65b1e688 100644 --- a/apps/studio/state/shortcuts/referenceGroups.ts +++ b/apps/studio/state/shortcuts/referenceGroups.ts @@ -1,4 +1,5 @@ export const SHORTCUT_REFERENCE_GROUPS = { + GLOBAL_ACTIONS: 'global.actions', NAVIGATION_GLOBAL: 'navigation.global', NAVIGATION_DATABASE: 'navigation.database', NAVIGATION_AUTH: 'navigation.auth', @@ -9,6 +10,7 @@ export const SHORTCUT_REFERENCE_GROUPS = { } as const export const SHORTCUT_REFERENCE_GROUP_LABELS: Record = { + [SHORTCUT_REFERENCE_GROUPS.GLOBAL_ACTIONS]: 'Global Actions', [SHORTCUT_REFERENCE_GROUPS.NAVIGATION_GLOBAL]: 'Global Navigation', [SHORTCUT_REFERENCE_GROUPS.NAVIGATION_DATABASE]: 'Database Navigation', [SHORTCUT_REFERENCE_GROUPS.NAVIGATION_AUTH]: 'Auth Navigation', @@ -21,6 +23,7 @@ export const SHORTCUT_REFERENCE_GROUP_LABELS: Record = { export const SHORTCUT_REFERENCE_GROUP_ORDER = [ 'command-menu', 'shortcuts', + SHORTCUT_REFERENCE_GROUPS.GLOBAL_ACTIONS, SHORTCUT_REFERENCE_GROUPS.NAVIGATION_GLOBAL, SHORTCUT_REFERENCE_GROUPS.NAVIGATION_DATABASE, SHORTCUT_REFERENCE_GROUPS.NAVIGATION_AUTH, diff --git a/apps/studio/state/shortcuts/registry.ts b/apps/studio/state/shortcuts/registry.ts index 39e45ec9d21..1c7cedcb3b2 100644 --- a/apps/studio/state/shortcuts/registry.ts +++ b/apps/studio/state/shortcuts/registry.ts @@ -76,6 +76,7 @@ export const SHORTCUT_IDS = { NAV_ORG_BILLING: 'nav.org-billing', NAV_ORG_SETTINGS: 'nav.org-settings', SHORTCUTS_OPEN_REFERENCE: 'shortcuts.open-reference', + CONNECT_OPEN_SHEET: 'connect.open-sheet', // Table editor shortcuts ...TABLE_EDITOR_SHORTCUT_IDS, @@ -160,11 +161,13 @@ export const SHORTCUT_DEFINITIONS: Record = { id: SHORTCUT_IDS.AI_ASSISTANT_TOGGLE, label: 'Toggle AI Assistant panel', sequence: ['Mod+I'], + referenceGroup: SHORTCUT_REFERENCE_GROUPS.GLOBAL_ACTIONS, }, [SHORTCUT_IDS.INLINE_EDITOR_TOGGLE]: { id: SHORTCUT_IDS.INLINE_EDITOR_TOGGLE, label: 'Toggle inline SQL editor', sequence: ['Mod+E'], + referenceGroup: SHORTCUT_REFERENCE_GROUPS.GLOBAL_ACTIONS, }, [SHORTCUT_IDS.RESULTS_COPY_MARKDOWN]: { id: SHORTCUT_IDS.RESULTS_COPY_MARKDOWN, @@ -386,6 +389,14 @@ export const SHORTCUT_DEFINITIONS: Record = { showInSettings: false, options: { ignoreInputs: true }, }, + [SHORTCUT_IDS.CONNECT_OPEN_SHEET]: { + id: SHORTCUT_IDS.CONNECT_OPEN_SHEET, + label: 'Open Connect sheet', + sequence: ['O', 'C'], + showInSettings: false, + referenceGroup: SHORTCUT_REFERENCE_GROUPS.GLOBAL_ACTIONS, + options: { ignoreInputs: true }, + }, // Table editor shortcut registration ...tableEditorRegistry, diff --git a/packages/common/telemetry-constants.ts b/packages/common/telemetry-constants.ts index 9d1357a4aa4..da9e845d688 100644 --- a/packages/common/telemetry-constants.ts +++ b/packages/common/telemetry-constants.ts @@ -1929,7 +1929,7 @@ export interface ConnectSheetOpenedEvent { /** * Where the sheet was opened from */ - source: 'header_button' | 'connect_section' + source: 'header_button' | 'connect_section' | 'keyboard_shortcut' } groups: TelemetryGroups }