diff --git a/apps/studio/components/interfaces/App/CommandMenu/RecentRoutes.tsx b/apps/studio/components/interfaces/App/CommandMenu/RecentRoutes.tsx
index c5edbb1f54b..63069dfbdc0 100644
--- a/apps/studio/components/interfaces/App/CommandMenu/RecentRoutes.tsx
+++ b/apps/studio/components/interfaces/App/CommandMenu/RecentRoutes.tsx
@@ -1,39 +1,126 @@
import { History } from 'lucide-react'
import { useRouter } from 'next/router'
-import { useMemo } from 'react'
+import { useCallback, useMemo } from 'react'
import { useParams } from 'common'
import { useRecentRoutes } from 'hooks/misc/useRecentRoutes'
-import { useRegisterCommands, useSetCommandMenuOpen } from 'ui-patterns/CommandMenu'
+import {
+ PageType,
+ useRegisterCommands,
+ useRegisterPage,
+ useSetCommandMenuOpen,
+ useSetPage,
+} from 'ui-patterns/CommandMenu'
import { COMMAND_MENU_SECTIONS } from './CommandMenu.utils'
import { orderCommandSectionsByPriority } from './ordering'
+const RECENTS_PAGE_NAME = 'Recent Pages'
+const VISIBLE_RECENTS_COUNT = 3
+
export function useRecentRoutesCommands() {
const router = useRouter()
const { ref } = useParams()
const setIsOpen = useSetCommandMenuOpen()
+ const setPage = useSetPage()
const { recentRoutes } = useRecentRoutes()
- const commands = useMemo(() => {
+ // Stable navigation function
+ const navigateTo = useCallback(
+ (link: string) => {
+ router.push(link)
+ setIsOpen(false)
+ },
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ []
+ )
+
+ // Stable page navigation
+ const goToAllRecents = useCallback(() => {
+ setPage(RECENTS_PAGE_NAME)
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [])
+
+ // Create command objects from recent routes - only depend on the route data, not callbacks
+ const allCommands = useMemo(() => {
return recentRoutes.map((route) => ({
id: `recent-${route.key}`,
name: route.childName,
- // Include parent label in value for search
value: route.parentLabel ? `${route.childName} ${route.parentLabel}` : route.childName,
- action: () => {
- router.push(route.link)
- setIsOpen(false)
- },
+ link: route.link,
+ parentLabel: route.parentLabel,
+ }))
+ }, [recentRoutes])
+
+ // Commands shown in the main menu (first 3)
+ const visibleCommands = useMemo(() => {
+ return allCommands.slice(0, VISIBLE_RECENTS_COUNT)
+ }, [allCommands])
+
+ // Convert to ICommand format with actions
+ const allCommandsWithActions = useMemo(() => {
+ return allCommands.map((cmd) => ({
+ id: cmd.id,
+ name: cmd.name,
+ value: cmd.value,
+ action: () => navigateTo(cmd.link),
icon: () => ,
- badge: route.parentLabel
- ? () => {route.parentLabel}
+ badge: cmd.parentLabel
+ ? () => {cmd.parentLabel}
: undefined,
}))
- }, [recentRoutes, router, setIsOpen])
+ }, [allCommands, navigateTo])
- useRegisterCommands(COMMAND_MENU_SECTIONS.RECENTS, commands, {
- enabled: !!ref && commands.length > 0,
- deps: [commands],
+ const visibleCommandsWithActions = useMemo(() => {
+ return visibleCommands.map((cmd) => ({
+ id: cmd.id,
+ name: cmd.name,
+ value: cmd.value,
+ action: () => navigateTo(cmd.link),
+ icon: () => ,
+ badge: cmd.parentLabel
+ ? () => {cmd.parentLabel}
+ : undefined,
+ }))
+ }, [visibleCommands, navigateTo])
+
+ // Register the "All recents" subpage with all commands
+ useRegisterPage(
+ RECENTS_PAGE_NAME,
+ {
+ type: PageType.Commands,
+ sections: [
+ {
+ id: 'all-recents',
+ name: 'Recent Pages',
+ commands: allCommandsWithActions,
+ },
+ ],
+ },
+ { deps: [allCommandsWithActions], enabled: !!ref && allCommands.length > VISIBLE_RECENTS_COUNT }
+ )
+
+ // Register the visible commands + "All recents..." link
+ const commandsWithAllRecents = useMemo(() => {
+ const commands = [...visibleCommandsWithActions]
+
+ // Add "All recents..." command if there are more than visible
+ if (allCommands.length > VISIBLE_RECENTS_COUNT) {
+ commands.push({
+ id: 'all-recents',
+ name: 'All recents...',
+ value: 'All recents recent pages history',
+ action: goToAllRecents,
+ icon: () => ,
+ badge: undefined,
+ })
+ }
+
+ return commands
+ }, [visibleCommandsWithActions, allCommands.length, goToAllRecents])
+
+ useRegisterCommands(COMMAND_MENU_SECTIONS.RECENTS, commandsWithAllRecents, {
+ enabled: !!ref && commandsWithAllRecents.length > 0,
+ deps: [commandsWithAllRecents],
orderSection: orderCommandSectionsByPriority,
sectionMeta: { priority: 1 },
})
diff --git a/apps/studio/hooks/misc/useRecentRoutes.ts b/apps/studio/hooks/misc/useRecentRoutes.ts
index fd177f4e3d6..34bd3825e67 100644
--- a/apps/studio/hooks/misc/useRecentRoutes.ts
+++ b/apps/studio/hooks/misc/useRecentRoutes.ts
@@ -1,5 +1,5 @@
import { LOCAL_STORAGE_KEYS, useParams } from 'common'
-import { useCallback, useMemo, useRef } from 'react'
+import { useCallback, useEffect, useMemo, useRef } from 'react'
import { useLocalStorageQuery } from './useLocalStorage'
export type RecentRoute = {
@@ -12,7 +12,7 @@ export type RecentRoute = {
visitedAt: number // Timestamp for ordering
}
-const MAX_RECENT_ROUTES = 5
+const MAX_RECENT_ROUTES = 15
const EXCLUDED_ROUTE_KEYS = ['HOME'] // Don't track Project Overview since it's always visible
const DEFAULT_RECENT_ROUTES: RecentRoute[] = []
@@ -20,12 +20,19 @@ const DEFAULT_RECENT_ROUTES: RecentRoute[] = []
export const useRecentRoutes = () => {
const { ref } = useParams()
const lastTrackedKeyRef = useRef(null)
+ const recentRoutesRef = useRef(DEFAULT_RECENT_ROUTES)
const [recentRoutes, setRecentRoutes, { isSuccess }] = useLocalStorageQuery(
LOCAL_STORAGE_KEYS.RECENT_SIDEBAR_ROUTES(ref ?? ''),
DEFAULT_RECENT_ROUTES
)
+ // Keep ref in sync with latest routes
+ useEffect(() => {
+ recentRoutesRef.current = recentRoutes
+ }, [recentRoutes])
+
+ // Stable trackRoute that uses ref for current routes
const trackRoute = useCallback(
(route: {
key: string
@@ -51,13 +58,15 @@ export const useRecentRoutes = () => {
}
lastTrackedKeyRef.current = route.key
+ const currentRoutes = recentRoutesRef.current
+
// Check if this route is already at the top - if so, don't update
- if (recentRoutes.length > 0 && recentRoutes[0].key === route.key) {
+ if (currentRoutes.length > 0 && currentRoutes[0].key === route.key) {
return
}
// Remove existing entry with the same key (to update visitedAt)
- const filteredRoutes = recentRoutes.filter((r) => r.key !== route.key)
+ const filteredRoutes = currentRoutes.filter((r) => r.key !== route.key)
// Add new entry at the beginning
const newRoute: RecentRoute = {
@@ -74,7 +83,7 @@ export const useRecentRoutes = () => {
const newRoutes = [newRoute, ...filteredRoutes].slice(0, MAX_RECENT_ROUTES)
setRecentRoutes(newRoutes)
},
- [recentRoutes, setRecentRoutes]
+ [setRecentRoutes]
)
// Sort by visitedAt descending (most recent first)