From 3ed436de747acc512319ca67cc90ef4f09b886db Mon Sep 17 00:00:00 2001 From: Ali Waseem Date: Fri, 17 Apr 2026 08:20:36 -0600 Subject: [PATCH] feat: new shortcuts hook with registrations (#44954) 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? - Brand new hook APIs for registering shortcuts using tanstack hotkeys - Support for command menu injection when shortcut is added ## Summary by CodeRabbit * **New Features** * Centralized keyboard shortcuts system with per‑shortcut registration and per‑user enable/disable preferences stored locally * Added a "Copy results as Markdown" shortcut (Mod+Shift+M) * Shortcuts can be surfaced in the Command Menu with a visual shortcut badge for discoverability * **Documentation** * Legacy keyboard shortcut hooks marked as deprecated and documentation updated to point to the new shortcut API --- .../grid/components/common/Hooks.tsx | 4 + .../App/CommandMenu/CommandMenu.utils.ts | 1 + apps/studio/hooks/ui/useHotKey.ts | 14 ++- apps/studio/package.json | 1 + apps/studio/state/shortcuts/registry.ts | 46 +++++++++ apps/studio/state/shortcuts/state.ts | 51 ++++++++++ apps/studio/state/shortcuts/types.ts | 80 ++++++++++++++++ .../state/shortcuts/useIsShortcutEnabled.ts | 20 ++++ apps/studio/state/shortcuts/useShortcut.tsx | 94 +++++++++++++++++++ packages/common/constants/local-storage.ts | 3 + pnpm-lock.yaml | 33 ++++++- 11 files changed, 341 insertions(+), 6 deletions(-) create mode 100644 apps/studio/state/shortcuts/registry.ts create mode 100644 apps/studio/state/shortcuts/state.ts create mode 100644 apps/studio/state/shortcuts/types.ts create mode 100644 apps/studio/state/shortcuts/useIsShortcutEnabled.ts create mode 100644 apps/studio/state/shortcuts/useShortcut.tsx diff --git a/apps/studio/components/grid/components/common/Hooks.tsx b/apps/studio/components/grid/components/common/Hooks.tsx index 2e081fa49cc..ef2fe369e31 100644 --- a/apps/studio/components/grid/components/common/Hooks.tsx +++ b/apps/studio/components/grid/components/common/Hooks.tsx @@ -7,6 +7,10 @@ function includes(array: string[], element: string) { /** * Hook for listening on key events. * + * @deprecated Use `useShortcut` from `state/shortcuts/useShortcut` instead. + * The new hook reads from a central registry, respects user preferences, and + * can surface shortcuts in the Cmd+P command menu. + * * @param {Object|Map} keyMap Key names mapped to event handlers. If a key name exists, its * default behavior will be suppressed. * @param {Array} whitelistNodes If target element is in the whitelist nodes array, will not diff --git a/apps/studio/components/interfaces/App/CommandMenu/CommandMenu.utils.ts b/apps/studio/components/interfaces/App/CommandMenu/CommandMenu.utils.ts index d203281c9d2..832332f31c7 100644 --- a/apps/studio/components/interfaces/App/CommandMenu/CommandMenu.utils.ts +++ b/apps/studio/components/interfaces/App/CommandMenu/CommandMenu.utils.ts @@ -7,4 +7,5 @@ export const COMMAND_MENU_SECTIONS = { SQL: 'SQL Editor', DATABASE: 'Database', INTEGRATIONS: 'Integrations', + SHORTCUTS: 'Shortcuts', } as const diff --git a/apps/studio/hooks/ui/useHotKey.ts b/apps/studio/hooks/ui/useHotKey.ts index 60dbe097c6a..cedb801e180 100644 --- a/apps/studio/hooks/ui/useHotKey.ts +++ b/apps/studio/hooks/ui/useHotKey.ts @@ -1,14 +1,24 @@ import { useEffect } from 'react' import { useLatest } from 'react-use' -// [Joshen] Refactor: Remove dependencies, and just make this into a single definition +/** + * @deprecated Use `useShortcut` from `state/shortcuts/useShortcut` instead. + * It reads from a central shortcut registry (`SHORTCUT_DEFINITIONS`) and + * integrates with the user's enable/disable preferences + the Cmd+P command menu. + * + * Migration: + * 1. Add an entry to `state/shortcuts/registry.ts` with a unique ID, label, and sequence. + * 2. Replace `useHotKey(cb, 'k', { shift: true })` with + * `useShortcut(SHORTCUT_IDS.YOUR_ID, cb)`. + */ function useHotKey( callback: (e: KeyboardEvent) => void, key: string, options?: { enabled?: boolean; shift?: boolean } ): void /** - * @deprecated The `dependencies` parameter is deprecated. Use the overload without dependencies instead. + * @deprecated Use `useShortcut` from `state/shortcuts/useShortcut` instead. + * The `dependencies` parameter is also deprecated in this legacy hook. */ function useHotKey( callback: (e: KeyboardEvent) => void, diff --git a/apps/studio/package.json b/apps/studio/package.json index 9da24a7d95a..ba7532a05d6 100644 --- a/apps/studio/package.json +++ b/apps/studio/package.json @@ -67,6 +67,7 @@ "@supabase/shared-types": "0.1.88", "@supabase/sql-to-rest": "^0.1.6", "@supabase/supabase-js": "catalog:", + "@tanstack/react-hotkeys": "^0.9.1", "@tanstack/react-query": "^5.0.0", "@tanstack/react-query-devtools": "^5.0.0", "@tanstack/react-table": "^8.21.3", diff --git a/apps/studio/state/shortcuts/registry.ts b/apps/studio/state/shortcuts/registry.ts new file mode 100644 index 00000000000..be08a32d51c --- /dev/null +++ b/apps/studio/state/shortcuts/registry.ts @@ -0,0 +1,46 @@ +import { ShortcutDefinition } from './types' + +/** + * The canonical list of shortcut IDs. Add new shortcuts here first, then + * register them in `SHORTCUT_DEFINITIONS` below. + * + * ID convention: `"."` in kebab-case, e.g. `"results.copy-markdown"`. + * The `` groups related shortcuts (sql-editor, table-editor, results, etc). + */ +export const SHORTCUT_IDS = { + RESULTS_COPY_MARKDOWN: 'results.copy-markdown', +} as const + +/** + * Union of all valid shortcut IDs. Use this as the `id` parameter type on any + * hook or util that takes a shortcut reference. + */ +export type ShortcutId = (typeof SHORTCUT_IDS)[keyof typeof SHORTCUT_IDS] + +/** + * The shortcut registry — every shortcut the app knows about, keyed by + * `ShortcutId`. The `Record` type ensures this map stays exhaustive: adding a + * new entry to `SHORTCUT_IDS` without a matching definition here is a type error. + * + * See `ShortcutDefinition` for the shape of each entry. + * + * @example + * // Add a new shortcut: + * // 1. Add to SHORTCUT_IDS: + * // SQL_EDITOR_RUN: 'sql-editor.run' + * // 2. Add to SHORTCUT_DEFINITIONS: + * // [SHORTCUT_IDS.SQL_EDITOR_RUN]: { + * // id: SHORTCUT_IDS.SQL_EDITOR_RUN, + * // label: 'Run query', + * // sequence: ['Mod+Enter'], + * // } + * // 3. Use in a component: + * // useShortcut(SHORTCUT_IDS.SQL_EDITOR_RUN, runQuery) + */ +export const SHORTCUT_DEFINITIONS: Record = { + [SHORTCUT_IDS.RESULTS_COPY_MARKDOWN]: { + id: SHORTCUT_IDS.RESULTS_COPY_MARKDOWN, + label: 'Copy results as Markdown', + sequence: ['Mod+Shift+M'], + }, +} diff --git a/apps/studio/state/shortcuts/state.ts b/apps/studio/state/shortcuts/state.ts new file mode 100644 index 00000000000..e2e97dce815 --- /dev/null +++ b/apps/studio/state/shortcuts/state.ts @@ -0,0 +1,51 @@ +import { LOCAL_STORAGE_KEYS } from 'common' +import { useCallback } from 'react' + +import type { ShortcutId } from './registry' +import { DisabledShortcuts } from './types' +import { useLocalStorageQuery } from '@/hooks/misc/useLocalStorage' + +const STORAGE_KEY = LOCAL_STORAGE_KEYS.SHORTCUT_STORAGE_KEY + +const DEFAULT_DISABLED: DisabledShortcuts = {} + +export function useShortcutPreferences() { + const [disabled, setDisabled] = useLocalStorageQuery( + STORAGE_KEY, + DEFAULT_DISABLED + ) + + const setShortcutEnabled = useCallback( + (id: ShortcutId, enabled: boolean) => { + setDisabled((prev) => { + if (enabled) { + const { [id]: _removed, ...rest } = prev + return rest + } + return { ...prev, [id]: true } + }) + }, + [setDisabled] + ) + + const resetShortcut = useCallback( + (id: ShortcutId) => { + setDisabled((prev) => { + const { [id]: _removed, ...rest } = prev + return rest + }) + }, + [setDisabled] + ) + + const resetAllShortcuts = useCallback(() => { + setDisabled(DEFAULT_DISABLED) + }, [setDisabled]) + + return { + disabled, + setShortcutEnabled, + resetShortcut, + resetAllShortcuts, + } +} diff --git a/apps/studio/state/shortcuts/types.ts b/apps/studio/state/shortcuts/types.ts new file mode 100644 index 00000000000..8ee1cd3249b --- /dev/null +++ b/apps/studio/state/shortcuts/types.ts @@ -0,0 +1,80 @@ +import { HotkeySequence } from '@tanstack/react-hotkeys' + +export type DisabledShortcuts = Record + +/** + * Runtime options for a shortcut. Used in two places: + * + * 1. On a `ShortcutDefinition` in the registry — acts as the default options + * whenever the shortcut is mounted via `useShortcut`. + * 2. As the third argument to `useShortcut(id, callback, options)` — lets the + * call site override the registry defaults for a specific mount. + * + * Caller options take priority over registry defaults, which take priority over + * the hard-coded fallbacks listed on each field below. + */ +export interface ShortcutOptions { + /** + * Whether the shortcut is live. Defaults to `true`. + * + * Conjunctive with the user's global enable/disable preference: if the user + * has disabled a shortcut in Account → Preferences, passing `enabled: true` + * here will NOT re-enable it. Use this to gate the shortcut on local + * conditions (e.g. `enabled: hasUnsavedChanges`). + */ + enabled?: boolean + + /** + * Maximum time in milliseconds between consecutive keys in a multi-step + * sequence. Defaults to `undefined`, which falls through to TanStack's + * library default (1000ms). + * + * Only meaningful for multi-step sequences like `['G', 'G']`. Single-step + * shortcuts ignore this. + */ + timeout?: number + + /** + * When `true`, the shortcut also appears as an entry in the Cmd+P command + * menu (under the "Shortcuts" section) for as long as the hook is mounted. + * The entry's label comes from `ShortcutDefinition.label` and the keybind + * is rendered from `ShortcutDefinition.sequence`. + * + * Defaults to `false` — opt-in per call site so Cmd+P doesn't fill up with + * context-specific shortcuts that only make sense in certain views. + */ + registerInCommandMenu?: boolean +} + +/** + * A single entry in the shortcut registry. Every shortcut the app uses must + * have a matching definition in `SHORTCUT_DEFINITIONS`. + * + * The registry is the single source of truth for: + * - the keybind (`sequence`) — used by the hotkey listener AND the Cmd+P badge + * - the human-readable `label` — shown in Cmd+P and in the preferences UI + * - per-shortcut default `options` — overridable per call site + */ +export interface ShortcutDefinition { + /** Stable unique identifier. Must match the key used in `SHORTCUT_IDS`. */ + id: string + + /** Human-readable label shown in the command menu and preferences UI. */ + label: string + + /** + * Keybind as a TanStack Hotkeys sequence — an array of one or more hotkey + * strings. Single-step: `['Mod+Shift+M']`. Multi-step (chord): `['G', 'G']`. + * + * Use `Mod` for the platform modifier (⌘ on macOS, Ctrl elsewhere). Supports + * `Shift`, `Alt`, `Ctrl` (literal), named keys (`Enter`, `Escape`, arrows), + * and single character keys. + */ + sequence: HotkeySequence + + /** + * Default runtime options applied when the shortcut is mounted. Each field + * is overridable by the caller of `useShortcut`. + */ + options?: ShortcutOptions +} diff --git a/apps/studio/state/shortcuts/useIsShortcutEnabled.ts b/apps/studio/state/shortcuts/useIsShortcutEnabled.ts new file mode 100644 index 00000000000..7518418d7d4 --- /dev/null +++ b/apps/studio/state/shortcuts/useIsShortcutEnabled.ts @@ -0,0 +1,20 @@ +import type { ShortcutId } from './registry' +import { useShortcutPreferences } from './state' + +/** + * Reactive check for whether a shortcut is currently enabled for the user. + * + * Subscribes the calling component to the shortcut preferences query so it + * re-renders when the user toggles this shortcut in Account → Preferences. + * + * For one-off, non-reactive checks outside render (event handlers, module-level + * code), use the plain `isShortcutEnabled(id)` function from `./state` instead. + * + * @example + * const isEnabled = useIsShortcutEnabled(SHORTCUT_IDS.AI_ASSISTANT_TOGGLE) + * return {isEnabled && } + */ +export function useIsShortcutEnabled(id: ShortcutId): boolean { + const { disabled } = useShortcutPreferences() + return !disabled[id] +} diff --git a/apps/studio/state/shortcuts/useShortcut.tsx b/apps/studio/state/shortcuts/useShortcut.tsx new file mode 100644 index 00000000000..c5a9faab23e --- /dev/null +++ b/apps/studio/state/shortcuts/useShortcut.tsx @@ -0,0 +1,94 @@ +import { useHotkeySequence } from '@tanstack/react-hotkeys' +import { Fragment, useCallback } from 'react' +import { KeyboardShortcut } from 'ui' +import { useRegisterCommands } from 'ui-patterns/CommandMenu' + +import { SHORTCUT_DEFINITIONS, type ShortcutId } from './registry' +import type { ShortcutOptions } from './types' +import { useIsShortcutEnabled } from './useIsShortcutEnabled' +import { COMMAND_MENU_SECTIONS } from '@/components/interfaces/App/CommandMenu/CommandMenu.utils' +import useLatest from '@/hooks/misc/useLatest' + +const hotkeyToKeys = (hotkey: string): string[] => + hotkey.split('+').map((part) => (part === 'Mod' ? 'Meta' : part)) + +/** + * Subscribe to a registered keyboard shortcut. + * + * Looks up the shortcut's `sequence` and `label` from `SHORTCUT_DEFINITIONS`, + * wires up a global hotkey listener via `@tanstack/react-hotkeys`, and + * (optionally) registers the shortcut as an entry in the Cmd+P command menu + * under the "Shortcuts" section for as long as the hook is mounted. + * + * Option resolution priority (highest first): + * 1. `options` passed to this hook + * 2. `def.options` from the registry entry + * 3. Hard-coded fallbacks (`enabled: true`, `timeout: undefined`, `registerInCommandMenu: false`) + * + * `enabled` is ANDed with the user's global enable/disable preference — if the + * user has disabled the shortcut in Preferences, it won't fire even if the + * caller or registry say `enabled: true`. + * + * @param id The registered shortcut to bind to. See `SHORTCUT_IDS`. + * @param callback Runs when the sequence matches. Always calls the latest + * reference — no stale closure issues. + * @param options Per-mount overrides. See `ShortcutOptions`. + * + * @example + * useShortcut(SHORTCUT_IDS.RESULTS_COPY_MARKDOWN, handleCopy) + * + * @example + * // Surface in Cmd+P while this component is mounted: + * useShortcut(SHORTCUT_IDS.SQL_EDITOR_RUN, runQuery, { + * registerInCommandMenu: true, + * }) + * + * @example + * // Gate on local state — disables hotkey AND hides Cmd+P entry when false: + * useShortcut(SHORTCUT_IDS.SAVE, handleSave, { + * enabled: hasUnsavedChanges, + * registerInCommandMenu: true, + * }) + */ +export function useShortcut(id: ShortcutId, callback: () => void, options?: ShortcutOptions) { + const def = SHORTCUT_DEFINITIONS[id] + + // Handle override for the shortcut + const globallyEnabled = useIsShortcutEnabled(id) + const callerEnabled = options?.enabled ?? def.options?.enabled ?? true + const enabled = globallyEnabled && callerEnabled + const timeout = options?.timeout ?? def.options?.timeout ?? undefined + + useHotkeySequence(def.sequence, callback, { enabled, timeout }) + + // Handle overrides for command menu + const enabledInCommandMenu = enabled && (options?.registerInCommandMenu ?? false) + const depsInCommandMenu = [enabled, def.label] + const callbackRef = useLatest(callback) + const stableAction = useCallback(() => callbackRef.current(), [callbackRef]) + + useRegisterCommands( + COMMAND_MENU_SECTIONS.SHORTCUTS, + [ + { + id, + name: def.label, + action: stableAction, + badge: () => ( +
+ {def.sequence.map((step, i) => ( + + {i > 0 && then} + + + ))} +
+ ), + }, + ], + { + enabled: enabledInCommandMenu, + deps: depsInCommandMenu, + } + ) +} diff --git a/packages/common/constants/local-storage.ts b/packages/common/constants/local-storage.ts index 34ae866c281..2f37b3dc6be 100644 --- a/packages/common/constants/local-storage.ts +++ b/packages/common/constants/local-storage.ts @@ -80,6 +80,9 @@ export const LOCAL_STORAGE_KEYS = { // api keys view switcher for new and legacy api keys API_KEYS_VIEW: (ref: string) => `supabase-api-keys-view-${ref}`, + // Shortcut preferences + SHORTCUT_STORAGE_KEY: 'supabase-shortcut-preferences', + LAST_VISITED_ORGANIZATION: 'last-visited-organization', // user impersonation selector previous searches diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 33f27f05861..7de592f0234 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -933,7 +933,7 @@ importers: version: 1.1.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@sentry/nextjs': specifier: 'catalog:' - version: 10.27.0(@opentelemetry/context-async-hooks@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@16.2.3(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1)(supports-color@8.1.1)(webpack@5.105.4(esbuild@0.25.2)) + version: 10.27.0(@opentelemetry/context-async-hooks@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@16.2.3(@babel/core@7.29.0(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1)(supports-color@8.1.1)(webpack@5.105.4(esbuild@0.25.2)) '@std/path': specifier: npm:@jsr/std__path@^1.0.8 version: '@jsr/std__path@1.0.8' @@ -967,6 +967,9 @@ importers: '@supabase/supabase-js': specifier: 'catalog:' version: 2.103.2 + '@tanstack/react-hotkeys': + specifier: ^0.9.1 + version: 0.9.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@tanstack/react-query': specifier: ^5.0.0 version: 5.83.0(react@18.3.1) @@ -1086,7 +1089,7 @@ importers: version: 0.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) nuqs: specifier: 2.7.1 - version: 2.7.1(@tanstack/react-router@1.168.18(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(next@16.2.3(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react-router@7.13.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) + version: 2.7.1(@tanstack/react-router@1.168.18(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(next@16.2.3(@babel/core@7.29.0(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react-router@7.13.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) openai: specifier: ^4.104.0 version: 4.104.0(encoding@0.1.13)(ws@8.19.0)(zod@3.25.76) @@ -8945,12 +8948,23 @@ packages: resolution: {integrity: sha512-NaOGLRrddszbQj9upGat6HG/4TKvXLvu+osAIgfxPYA+eIvYKv8GKDJOrY2D3/U9MRnKfMWD7bU4jeD4xmqyIg==} engines: {node: '>=20.19'} + '@tanstack/hotkeys@0.7.1': + resolution: {integrity: sha512-YHVO1z6wnvUCu7bg870Kv5k2D+FIuIOSIcbN0dAmTTsJ3mLMDLwcTVx0qVaq+SZp1B514JJTqGVstvUp85yIpQ==} + engines: {node: '>=18'} + '@tanstack/query-core@5.83.0': resolution: {integrity: sha512-0M8dA+amXUkyz5cVUm/B+zSk3xkQAcuXuz5/Q/LveT4ots2rBpPTZOzd7yJa2Utsf8D2Upl5KyjhHRY+9lB/XA==} '@tanstack/query-devtools@5.90.1': resolution: {integrity: sha512-GtINOPjPUH0OegJExZ70UahT9ykmAhmtNVcmtdnOZbxLwT7R5OmRztR5Ahe3/Cu7LArEmR6/588tAycuaWb1xQ==} + '@tanstack/react-hotkeys@0.9.1': + resolution: {integrity: sha512-/qdQUUVkYAHAWRGdFXqFgWpW/S+a6OzkvxWNWKLLDHQODJlO6EPBPa073CglaafBfzig58RK07T09ET+NnZhpg==} + engines: {node: '>=18'} + peerDependencies: + react: '>=16.8' + react-dom: '>=16.8' + '@tanstack/react-query-devtools@5.90.2': resolution: {integrity: sha512-vAXJzZuBXtCQtrY3F/yUNJCV4obT/A/n81kb3+YqLbro5Z2+phdAbceO+deU3ywPw8B42oyJlp4FhO0SoivDFQ==} peerDependencies: @@ -25761,7 +25775,7 @@ snapshots: - supports-color - webpack - '@sentry/nextjs@10.27.0(@opentelemetry/context-async-hooks@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@16.2.3(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1)(supports-color@8.1.1)(webpack@5.105.4(esbuild@0.25.2))': + '@sentry/nextjs@10.27.0(@opentelemetry/context-async-hooks@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.2.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@16.2.3(@babel/core@7.29.0(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react@18.3.1)(supports-color@8.1.1)(webpack@5.105.4(esbuild@0.25.2))': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/semantic-conventions': 1.38.0 @@ -26519,10 +26533,21 @@ snapshots: '@tanstack/history@1.161.6': {} + '@tanstack/hotkeys@0.7.1': + dependencies: + '@tanstack/store': 0.9.3 + '@tanstack/query-core@5.83.0': {} '@tanstack/query-devtools@5.90.1': {} + '@tanstack/react-hotkeys@0.9.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@tanstack/hotkeys': 0.7.1 + '@tanstack/react-store': 0.9.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + '@tanstack/react-query-devtools@5.90.2(@tanstack/react-query@5.83.0(react@18.3.1))(react@18.3.1)': dependencies: '@tanstack/query-devtools': 5.90.1 @@ -34199,7 +34224,7 @@ snapshots: mitt: 3.0.1 next: 15.5.15(@babel/core@7.29.0(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4) - nuqs@2.7.1(@tanstack/react-router@1.168.18(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(next@16.2.3(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react-router@7.13.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1): + nuqs@2.7.1(@tanstack/react-router@1.168.18(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(next@16.2.3(@babel/core@7.29.0(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.4))(react-router@7.13.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1): dependencies: '@standard-schema/spec': 1.0.0 react: 18.3.1