From 49a9fdeabcfcc4d8ada827e518650350b6ff7f9f Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Wed, 4 Feb 2026 11:08:33 +0700 Subject: [PATCH] Fix keyboard shortcuts and DRY agent status dot --- packages/app/src/app/_layout.tsx | 3 ++ packages/app/src/components/agent-list.tsx | 20 ++--------- .../app/src/components/agent-status-dot.tsx | 34 +++++++++++++++++++ .../app/src/components/command-center.tsx | 11 +++++- .../app/src/components/grouped-agent-list.tsx | 19 ++--------- .../app/src/hooks/use-global-keyboard-nav.ts | 28 +++++++++++++-- 6 files changed, 77 insertions(+), 38 deletions(-) create mode 100644 packages/app/src/components/agent-status-dot.tsx diff --git a/packages/app/src/app/_layout.tsx b/packages/app/src/app/_layout.tsx index d03b263e6..7bfe93458 100644 --- a/packages/app/src/app/_layout.tsx +++ b/packages/app/src/app/_layout.tsx @@ -125,6 +125,7 @@ function AppContainer({ children, selectedAgentId }: AppContainerProps) { const desktopAgentListOpen = usePanelStore((state) => state.desktop.agentListOpen); const openAgentList = usePanelStore((state) => state.openAgentList); const toggleAgentList = usePanelStore((state) => state.toggleAgentList); + const toggleFileExplorer = usePanelStore((state) => state.toggleFileExplorer); const horizontalScroll = useHorizontalScrollOptional(); const isMobile = @@ -140,6 +141,8 @@ function AppContainer({ children, selectedAgentId }: AppContainerProps) { enabled: chromeEnabled, isMobile, toggleAgentList, + selectedAgentId, + toggleFileExplorer, }); const { translateX, diff --git a/packages/app/src/components/agent-list.tsx b/packages/app/src/components/agent-list.tsx index f5ed5eba1..0112ba9e9 100644 --- a/packages/app/src/components/agent-list.tsx +++ b/packages/app/src/components/agent-list.tsx @@ -18,6 +18,7 @@ import { shortenPath } from "@/utils/shorten-path"; import { deriveBranchLabel, deriveProjectPath } from "@/utils/agent-display-info"; import { type AggregatedAgent } from "@/hooks/use-aggregated-agents"; import { useSessionStore } from "@/stores/session-store"; +import { AgentStatusDot } from "@/components/agent-status-dot"; import { CHECKOUT_STATUS_STALE_TIME, checkoutStatusQueryKey, @@ -181,12 +182,6 @@ export function AgentList({ const timeAgo = formatTimeAgo(agent.lastActivityAt); const agentKey = `${agent.serverId}:${agent.id}`; const isSelected = selectedAgentId === agentKey; - const isRunning = agent.status === "running"; - const statusColor = isRunning - ? theme.colors.palette.blue[500] - : agent.requiresAttention - ? theme.colors.success - : null; const checkoutQuery = useCheckoutStatusCacheOnly({ serverId: agent.serverId, @@ -211,11 +206,7 @@ export function AgentList({ {({ hovered }) => ( - {statusColor && ( - - )} + ({ fontWeight: "300", color: theme.colors.foregroundMuted, }, - statusDot: { - width: 8, - height: 8, - borderRadius: theme.borderRadius.full, - }, sheetOverlay: { flex: 1, justifyContent: "flex-end", diff --git a/packages/app/src/components/agent-status-dot.tsx b/packages/app/src/components/agent-status-dot.tsx new file mode 100644 index 000000000..2e0f32665 --- /dev/null +++ b/packages/app/src/components/agent-status-dot.tsx @@ -0,0 +1,34 @@ +import { View } from "react-native"; +import { StyleSheet, useUnistyles } from "react-native-unistyles"; + +export function AgentStatusDot({ + status, + requiresAttention, +}: { + status: string | null | undefined; + requiresAttention: boolean | null | undefined; +}) { + const { theme } = useUnistyles(); + + const isRunning = status === "running"; + const color = isRunning + ? theme.colors.palette.blue[500] + : requiresAttention + ? theme.colors.success + : null; + + if (!color) { + return null; + } + + return ; +} + +const styles = StyleSheet.create((theme) => ({ + dot: { + width: 8, + height: 8, + borderRadius: theme.borderRadius.full, + }, +})); + diff --git a/packages/app/src/components/command-center.tsx b/packages/app/src/components/command-center.tsx index b1dc8cd04..5a2ce25eb 100644 --- a/packages/app/src/components/command-center.tsx +++ b/packages/app/src/components/command-center.tsx @@ -15,6 +15,7 @@ import { useAggregatedAgents, type AggregatedAgent } from "@/hooks/use-aggregate import { formatTimeAgo } from "@/utils/time"; import { shortenPath } from "@/utils/shorten-path"; import { useSessionStore } from "@/stores/session-store"; +import { AgentStatusDot } from "@/components/agent-status-dot"; function agentKey(agent: Pick): string { return `${agent.serverId}:${agent.id}`; @@ -188,12 +189,15 @@ export function CommandCenter() { onPress={() => handleSelect(agent)} > + + {agent.title || "New agent"} + ({ rowContent: { gap: 2, }, + rowTitle: { + flexDirection: "row", + alignItems: "center", + gap: theme.spacing[2], + }, title: { fontSize: theme.fontSize.base, - fontWeight: theme.fontWeight.medium, + fontWeight: theme.fontWeight.normal, }, subtitle: { fontSize: theme.fontSize.sm, diff --git a/packages/app/src/components/grouped-agent-list.tsx b/packages/app/src/components/grouped-agent-list.tsx index fadee8131..c2a5592f4 100644 --- a/packages/app/src/components/grouped-agent-list.tsx +++ b/packages/app/src/components/grouped-agent-list.tsx @@ -44,6 +44,7 @@ import { useSidebarAgentSections, type SidebarSectionData } from "@/hooks/use-si import { useSidebarCollapsedSectionsStore } from "@/stores/sidebar-collapsed-sections-store"; import { useKeyboardNavStore } from "@/stores/keyboard-nav-store"; import { getIsTauri } from "@/constants/layout"; +import { AgentStatusDot } from "@/components/agent-status-dot"; type SectionData = SidebarSectionData; @@ -339,11 +340,6 @@ export function GroupedAgentList({ const isRunning = agent.status === "running"; const shortcutNumber = showShortcutBadges ? (shortcutIndexByAgentKey.get(agentKey) ?? null) : null; - const statusColor = isRunning - ? theme.colors.palette.blue[500] - : agent.requiresAttention - ? theme.colors.success - : null; const checkoutQuery = useCheckoutStatusCacheOnly({ serverId: agent.serverId, @@ -377,11 +373,7 @@ export function GroupedAgentList({ > - {statusColor && ( - - )} + ({ fontWeight: "300", color: theme.colors.foregroundMuted, }, - statusDot: { - width: 8, - height: 8, - borderRadius: theme.borderRadius.full, - }, sheetOverlay: { flex: 1, justifyContent: "flex-end", diff --git a/packages/app/src/hooks/use-global-keyboard-nav.ts b/packages/app/src/hooks/use-global-keyboard-nav.ts index c6592d91b..83af1b38d 100644 --- a/packages/app/src/hooks/use-global-keyboard-nav.ts +++ b/packages/app/src/hooks/use-global-keyboard-nav.ts @@ -9,10 +9,14 @@ export function useGlobalKeyboardNav({ enabled, isMobile, toggleAgentList, + selectedAgentId, + toggleFileExplorer, }: { enabled: boolean; isMobile: boolean; toggleAgentList: () => void; + selectedAgentId?: string; + toggleFileExplorer?: () => void; }) { const router = useRouter(); const pathname = usePathname(); @@ -88,6 +92,18 @@ export function useGlobalKeyboardNav({ return; } + // Cmd+E: toggle explorer sidebar (only when an agent is selected) + if ( + selectedAgentId && + toggleFileExplorer && + (event.metaKey || event.ctrlKey) && + lowerKey === "e" + ) { + event.preventDefault(); + toggleFileExplorer(); + return; + } + // Cmd+K: command center if ((event.metaKey || event.ctrlKey) && lowerKey === "k") { event.preventDefault(); @@ -145,6 +161,14 @@ export function useGlobalKeyboardNav({ window.removeEventListener("blur", handleBlurOrHide); document.removeEventListener("visibilitychange", handleBlurOrHide); }; - }, [enabled, isMobile, pathname, resetModifiers, router, toggleAgentList]); + }, [ + enabled, + isMobile, + pathname, + resetModifiers, + router, + selectedAgentId, + toggleAgentList, + toggleFileExplorer, + ]); } -