Fix keyboard shortcuts and DRY agent status dot

This commit is contained in:
Mohamed Boudra
2026-02-04 11:08:33 +07:00
parent 4887592ebe
commit 49a9fdeabc
6 changed files with 77 additions and 38 deletions

View File

@@ -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,

View File

@@ -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 }) => (
<View style={styles.agentContent}>
<View style={styles.row}>
{statusColor && (
<View
style={[styles.statusDot, { backgroundColor: statusColor }]}
/>
)}
<AgentStatusDot status={agent.status} requiresAttention={agent.requiresAttention} />
<Text
style={[
styles.agentTitle,
@@ -240,8 +231,6 @@ export function AgentList({
handleAgentLongPress,
handleAgentPress,
selectedAgentId,
theme.colors.palette.blue,
theme.colors.success,
]
);
@@ -429,11 +418,6 @@ const styles = StyleSheet.create((theme) => ({
fontWeight: "300",
color: theme.colors.foregroundMuted,
},
statusDot: {
width: 8,
height: 8,
borderRadius: theme.borderRadius.full,
},
sheetOverlay: {
flex: 1,
justifyContent: "flex-end",

View File

@@ -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 <View style={[styles.dot, { backgroundColor: color }]} />;
}
const styles = StyleSheet.create((theme) => ({
dot: {
width: 8,
height: 8,
borderRadius: theme.borderRadius.full,
},
}));

View File

@@ -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<AggregatedAgent, "serverId" | "id">): string {
return `${agent.serverId}:${agent.id}`;
@@ -188,12 +189,15 @@ export function CommandCenter() {
onPress={() => handleSelect(agent)}
>
<View style={styles.rowContent}>
<View style={styles.rowTitle}>
<AgentStatusDot status={agent.status} requiresAttention={agent.requiresAttention} />
<Text
style={[styles.title, { color: theme.colors.foreground }]}
numberOfLines={1}
>
{agent.title || "New agent"}
</Text>
</View>
<Text
style={[styles.subtitle, { color: theme.colors.foregroundMuted }]}
numberOfLines={1}
@@ -258,9 +262,14 @@ const styles = StyleSheet.create((theme) => ({
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,

View File

@@ -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({
>
<View style={styles.agentContent}>
<View style={styles.row}>
{statusColor && (
<View
style={[styles.statusDot, { backgroundColor: statusColor }]}
/>
)}
<AgentStatusDot status={agent.status} requiresAttention={agent.requiresAttention} />
<Text
style={[
styles.agentTitle,
@@ -437,8 +429,6 @@ export function GroupedAgentList({
shortcutIndexByAgentKey,
theme.colors.foreground,
theme.colors.foregroundMuted,
theme.colors.palette.blue,
theme.colors.success,
]
);
@@ -691,11 +681,6 @@ const styles = StyleSheet.create((theme) => ({
fontWeight: "300",
color: theme.colors.foregroundMuted,
},
statusDot: {
width: 8,
height: 8,
borderRadius: theme.borderRadius.full,
},
sheetOverlay: {
flex: 1,
justifyContent: "flex-end",

View File

@@ -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,
]);
}