feat: add project-grouped agent fetch with checkout-lite placement
This commit is contained in:
@@ -0,0 +1,819 @@
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
Pressable,
|
||||
Modal,
|
||||
Image,
|
||||
Platform,
|
||||
} from "react-native";
|
||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
import {
|
||||
useCallback,
|
||||
useMemo,
|
||||
useState,
|
||||
useEffect,
|
||||
useRef,
|
||||
type ReactElement,
|
||||
type MutableRefObject,
|
||||
} from "react";
|
||||
import { router, usePathname } from "expo-router";
|
||||
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
||||
import { type GestureType } from "react-native-gesture-handler";
|
||||
import { Archive, ChevronDown, ChevronRight, Plus } from "lucide-react-native";
|
||||
import {
|
||||
DraggableList,
|
||||
type DraggableRenderItemInfo,
|
||||
} from "./draggable-list";
|
||||
import { parseRepoNameFromRemoteUrl, parseRepoShortNameFromRemoteUrl } from "@/utils/agent-grouping";
|
||||
import { type AggregatedAgent } from "@/hooks/use-aggregated-agents";
|
||||
import { useSessionStore } from "@/stores/session-store";
|
||||
import {
|
||||
buildAgentNavigationKey,
|
||||
startNavigationTiming,
|
||||
} from "@/utils/navigation-timing";
|
||||
import {
|
||||
useSectionOrderStore,
|
||||
} from "@/stores/section-order-store";
|
||||
import { useProjectIconQuery } from "@/hooks/use-project-icon-query";
|
||||
import {
|
||||
type SidebarCheckoutLite,
|
||||
type SidebarSectionData,
|
||||
} from "@/hooks/use-sidebar-agents-grouped";
|
||||
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;
|
||||
|
||||
interface SidebarAgentListProps {
|
||||
sections: SidebarSectionData[];
|
||||
checkoutByAgentKey: Map<string, SidebarCheckoutLite>;
|
||||
isRefreshing?: boolean;
|
||||
onRefresh?: () => void;
|
||||
selectedAgentId?: string;
|
||||
onAgentSelect?: () => void;
|
||||
listFooterComponent?: ReactElement | null;
|
||||
/** Gesture ref for coordinating with parent gestures (e.g., sidebar close) */
|
||||
parentGestureRef?: MutableRefObject<GestureType | undefined>;
|
||||
}
|
||||
|
||||
interface SectionHeaderProps {
|
||||
section: SectionData;
|
||||
checkout: SidebarCheckoutLite | null;
|
||||
isCollapsed: boolean;
|
||||
onToggle: () => void;
|
||||
onCreateAgent: (workingDir: string) => void;
|
||||
onDrag: () => void;
|
||||
isDragging: boolean;
|
||||
}
|
||||
|
||||
function SectionHeader({
|
||||
section,
|
||||
checkout,
|
||||
isCollapsed,
|
||||
onToggle,
|
||||
onCreateAgent,
|
||||
onDrag,
|
||||
isDragging,
|
||||
}: SectionHeaderProps) {
|
||||
const { theme } = useUnistyles();
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
|
||||
// Get project icon
|
||||
const iconQuery = useProjectIconQuery({
|
||||
serverId: section.firstAgentServerId ?? "",
|
||||
cwd: section.workingDir ?? "",
|
||||
});
|
||||
const icon = iconQuery.icon;
|
||||
|
||||
// Derive display title: prefer repo name from remote URL, fallback to path-based name
|
||||
let displayTitle = section.title;
|
||||
if (checkout?.isGit && checkout.remoteUrl) {
|
||||
const isGitHubRemote =
|
||||
checkout.remoteUrl.includes("github.com") ||
|
||||
checkout.remoteUrl.includes("git@github.com:");
|
||||
|
||||
const repoName = isGitHubRemote
|
||||
? parseRepoNameFromRemoteUrl(checkout.remoteUrl)
|
||||
: parseRepoShortNameFromRemoteUrl(checkout.remoteUrl);
|
||||
|
||||
if (repoName) {
|
||||
displayTitle = repoName;
|
||||
}
|
||||
}
|
||||
|
||||
const createAgentWorkingDir =
|
||||
(checkout?.isPaseoOwnedWorktree ? checkout.mainRepoRoot : null) ?? section.workingDir;
|
||||
|
||||
const handleCreatePress = useCallback(
|
||||
(e: { stopPropagation: () => void }) => {
|
||||
e.stopPropagation();
|
||||
if (createAgentWorkingDir) {
|
||||
onCreateAgent(createAgentWorkingDir);
|
||||
}
|
||||
},
|
||||
[onCreateAgent, createAgentWorkingDir]
|
||||
);
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
style={({ pressed }) => [
|
||||
styles.sectionHeader,
|
||||
isHovered && styles.sectionHeaderHovered,
|
||||
pressed && styles.sectionHeaderPressed,
|
||||
!isCollapsed && styles.sectionHeaderExpanded,
|
||||
isDragging && styles.sectionHeaderDragging,
|
||||
Platform.OS === "web" && ({ cursor: "grab" } as any),
|
||||
]}
|
||||
onPress={onToggle}
|
||||
onLongPress={onDrag}
|
||||
delayLongPress={200}
|
||||
onHoverIn={() => setIsHovered(true)}
|
||||
onHoverOut={() => setIsHovered(false)}
|
||||
>
|
||||
<View style={styles.sectionHeaderLeft}>
|
||||
<View style={styles.chevron}>
|
||||
{isCollapsed ? (
|
||||
<ChevronRight size={14} color={theme.colors.foregroundMuted} />
|
||||
) : (
|
||||
<ChevronDown size={14} color={theme.colors.foregroundMuted} />
|
||||
)}
|
||||
</View>
|
||||
{icon ? (
|
||||
<Image
|
||||
source={{ uri: `data:${icon.mimeType};base64,${icon.data}` }}
|
||||
style={styles.projectIcon}
|
||||
/>
|
||||
) : (
|
||||
<View style={styles.projectIconPlaceholder}>
|
||||
<Text style={styles.projectIconPlaceholderText}>
|
||||
{(section.workingDir?.split("/").pop() ?? displayTitle).charAt(0).toUpperCase()}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
<Text style={styles.sectionTitle} numberOfLines={1}>
|
||||
{displayTitle}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.sectionHeaderRight}>
|
||||
{createAgentWorkingDir && (
|
||||
<Pressable
|
||||
style={styles.createAgentButton}
|
||||
onPress={handleCreatePress}
|
||||
hitSlop={20}
|
||||
onStartShouldSetResponder={() => true}
|
||||
onStartShouldSetResponderCapture={() => true}
|
||||
>
|
||||
{({ hovered, pressed }) => (
|
||||
<Plus
|
||||
size={18}
|
||||
color={hovered || pressed ? theme.colors.foreground : theme.colors.foregroundMuted}
|
||||
/>
|
||||
)}
|
||||
</Pressable>
|
||||
)}
|
||||
</View>
|
||||
</Pressable>
|
||||
);
|
||||
}
|
||||
|
||||
interface SidebarAgentRowProps {
|
||||
agent: AggregatedAgent;
|
||||
checkout: SidebarCheckoutLite | null;
|
||||
isSelected: boolean;
|
||||
shortcutNumber: number | null;
|
||||
onPress: () => void;
|
||||
onLongPress: () => void;
|
||||
onArchive: (e: { stopPropagation: () => void }) => void;
|
||||
}
|
||||
|
||||
function SidebarAgentRow({
|
||||
agent,
|
||||
checkout,
|
||||
isSelected,
|
||||
shortcutNumber,
|
||||
onPress,
|
||||
onLongPress,
|
||||
onArchive,
|
||||
}: SidebarAgentRowProps) {
|
||||
const { theme } = useUnistyles();
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
const [isArchiveHovered, setIsArchiveHovered] = useState(false);
|
||||
const [isArchiveConfirmVisible, setIsArchiveConfirmVisible] = useState(false);
|
||||
const hoverOutTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
|
||||
const clearHoverOutTimeout = useCallback(() => {
|
||||
if (!hoverOutTimeoutRef.current) {
|
||||
return;
|
||||
}
|
||||
clearTimeout(hoverOutTimeoutRef.current);
|
||||
hoverOutTimeoutRef.current = null;
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
return () => clearHoverOutTimeout();
|
||||
}, [clearHoverOutTimeout]);
|
||||
|
||||
const handleHoverIn = useCallback(() => {
|
||||
clearHoverOutTimeout();
|
||||
setIsHovered(true);
|
||||
}, [clearHoverOutTimeout]);
|
||||
|
||||
const handleHoverOut = useCallback(() => {
|
||||
clearHoverOutTimeout();
|
||||
hoverOutTimeoutRef.current = setTimeout(() => {
|
||||
setIsHovered(false);
|
||||
setIsArchiveHovered(false);
|
||||
setIsArchiveConfirmVisible(false);
|
||||
}, 50);
|
||||
}, [clearHoverOutTimeout]);
|
||||
const activeBranchLabel = checkout?.isGit
|
||||
? ((checkout.currentBranch && checkout.currentBranch !== "HEAD"
|
||||
? checkout.currentBranch
|
||||
: null) ??
|
||||
"git")
|
||||
: null;
|
||||
|
||||
const canArchive = agent.status !== "running" && !agent.requiresAttention;
|
||||
const showArchive = canArchive &&
|
||||
shortcutNumber === null &&
|
||||
(isHovered || isArchiveHovered || isArchiveConfirmVisible);
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
style={({ pressed }) => [
|
||||
styles.agentItem,
|
||||
!isSelected && styles.agentItemUnselected,
|
||||
isSelected && styles.agentItemSelected,
|
||||
isHovered && styles.agentItemHovered,
|
||||
pressed && styles.agentItemPressed,
|
||||
]}
|
||||
onPress={onPress}
|
||||
onPressIn={() => {
|
||||
if (Platform.OS !== "web") {
|
||||
return;
|
||||
}
|
||||
handleHoverIn();
|
||||
}}
|
||||
onLongPress={onLongPress}
|
||||
onHoverIn={handleHoverIn}
|
||||
onHoverOut={handleHoverOut}
|
||||
testID={`agent-row-${agent.serverId}-${agent.id}`}
|
||||
>
|
||||
<View style={styles.agentContent}>
|
||||
<View style={styles.row}>
|
||||
<AgentStatusDot status={agent.status} requiresAttention={agent.requiresAttention} />
|
||||
<Text
|
||||
style={[
|
||||
styles.agentTitle,
|
||||
(isSelected || isHovered) && styles.agentTitleHighlighted,
|
||||
]}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{agent.title || "New agent"}
|
||||
</Text>
|
||||
{shortcutNumber !== null ? (
|
||||
<View style={styles.branchBadge}>
|
||||
<Text style={styles.branchBadgeText} numberOfLines={1}>
|
||||
{shortcutNumber}
|
||||
</Text>
|
||||
</View>
|
||||
) : showArchive ? (
|
||||
<Pressable
|
||||
style={[
|
||||
styles.branchBadge,
|
||||
isArchiveConfirmVisible && styles.archiveConfirmBadge,
|
||||
]}
|
||||
onHoverIn={() => {
|
||||
clearHoverOutTimeout();
|
||||
setIsHovered(true);
|
||||
setIsArchiveHovered(true);
|
||||
}}
|
||||
onHoverOut={() => {
|
||||
setIsArchiveHovered(false);
|
||||
}}
|
||||
onPress={(e) => {
|
||||
e.stopPropagation();
|
||||
if (!isArchiveConfirmVisible) {
|
||||
setIsArchiveConfirmVisible(true);
|
||||
return;
|
||||
}
|
||||
onArchive(e);
|
||||
setIsArchiveConfirmVisible(false);
|
||||
}}
|
||||
testID={
|
||||
isArchiveConfirmVisible
|
||||
? `agent-archive-confirm-${agent.serverId}-${agent.id}`
|
||||
: `agent-archive-${agent.serverId}-${agent.id}`
|
||||
}
|
||||
>
|
||||
{({ hovered: archiveHovered }) =>
|
||||
isArchiveConfirmVisible ? (
|
||||
<Text
|
||||
style={[
|
||||
styles.branchBadgeText,
|
||||
styles.archiveConfirmText,
|
||||
archiveHovered && styles.archiveConfirmTextHovered,
|
||||
]}
|
||||
numberOfLines={1}
|
||||
>
|
||||
Confirm
|
||||
</Text>
|
||||
) : (
|
||||
<Archive
|
||||
size={12}
|
||||
color={
|
||||
archiveHovered ? theme.colors.foreground : theme.colors.foregroundMuted
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</Pressable>
|
||||
) : activeBranchLabel ? (
|
||||
<View style={styles.branchBadge}>
|
||||
<Text style={styles.branchBadgeText} numberOfLines={1}>
|
||||
{activeBranchLabel}
|
||||
</Text>
|
||||
</View>
|
||||
) : null}
|
||||
</View>
|
||||
</View>
|
||||
</Pressable>
|
||||
);
|
||||
}
|
||||
|
||||
export function SidebarAgentList({
|
||||
sections,
|
||||
checkoutByAgentKey,
|
||||
isRefreshing = false,
|
||||
onRefresh,
|
||||
selectedAgentId,
|
||||
onAgentSelect,
|
||||
listFooterComponent,
|
||||
parentGestureRef,
|
||||
}: SidebarAgentListProps) {
|
||||
const { theme } = useUnistyles();
|
||||
const pathname = usePathname();
|
||||
const insets = useSafeAreaInsets();
|
||||
const [actionAgent, setActionAgent] = useState<AggregatedAgent | null>(null);
|
||||
|
||||
const collapsedProjectKeys = useSidebarCollapsedSectionsStore((s) => s.collapsedProjectKeys);
|
||||
const toggleProjectCollapsed = useSidebarCollapsedSectionsStore((s) => s.toggleProjectCollapsed);
|
||||
|
||||
const altDown = useKeyboardNavStore((s) => s.altDown);
|
||||
const cmdOrCtrlDown = useKeyboardNavStore((s) => s.cmdOrCtrlDown);
|
||||
const sidebarShortcutAgentKeys = useKeyboardNavStore((s) => s.sidebarShortcutAgentKeys);
|
||||
const isTauri = getIsTauri();
|
||||
const showShortcutBadges = altDown || (isTauri && cmdOrCtrlDown);
|
||||
const shortcutIndexByAgentKey = useMemo(() => {
|
||||
const map = new Map<string, number>();
|
||||
for (let i = 0; i < sidebarShortcutAgentKeys.length; i++) {
|
||||
const key = sidebarShortcutAgentKeys[i];
|
||||
if (!key) continue;
|
||||
map.set(key, i + 1);
|
||||
}
|
||||
return map;
|
||||
}, [sidebarShortcutAgentKeys]);
|
||||
|
||||
const actionClient = useSessionStore((state) =>
|
||||
actionAgent?.serverId ? state.sessions[actionAgent.serverId]?.client ?? null : null
|
||||
);
|
||||
|
||||
const isActionSheetVisible = actionAgent !== null;
|
||||
const isActionDaemonUnavailable = Boolean(actionAgent?.serverId && !actionClient);
|
||||
|
||||
const handleAgentPress = useCallback(
|
||||
(serverId: string, agentId: string) => {
|
||||
if (isActionSheetVisible) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear attention flag when opening agent
|
||||
const session = useSessionStore.getState().sessions[serverId];
|
||||
if (session?.client) {
|
||||
session.client.clearAgentAttention(agentId);
|
||||
}
|
||||
|
||||
const navigationKey = buildAgentNavigationKey(serverId, agentId);
|
||||
startNavigationTiming(navigationKey, {
|
||||
from: "home",
|
||||
to: "agent",
|
||||
params: { serverId, agentId },
|
||||
});
|
||||
|
||||
const shouldReplace = pathname.startsWith("/agent/");
|
||||
const navigate = shouldReplace ? router.replace : router.push;
|
||||
|
||||
onAgentSelect?.();
|
||||
|
||||
navigate(`/agent/${serverId}/${agentId}` as any);
|
||||
},
|
||||
[isActionSheetVisible, pathname, onAgentSelect]
|
||||
);
|
||||
|
||||
const handleAgentLongPress = useCallback((agent: AggregatedAgent) => {
|
||||
setActionAgent(agent);
|
||||
}, []);
|
||||
|
||||
const handleCloseActionSheet = useCallback(() => {
|
||||
setActionAgent(null);
|
||||
}, []);
|
||||
|
||||
const handleArchiveFromSheet = useCallback(() => {
|
||||
if (!actionAgent || !actionClient) {
|
||||
return;
|
||||
}
|
||||
void actionClient.archiveAgent(actionAgent.id);
|
||||
setActionAgent(null);
|
||||
}, [actionAgent, actionClient]);
|
||||
|
||||
const handleCreateAgentInProject = useCallback(
|
||||
(workingDir: string) => {
|
||||
onAgentSelect?.();
|
||||
router.push(`/agent?workingDir=${encodeURIComponent(workingDir)}` as any);
|
||||
},
|
||||
[onAgentSelect]
|
||||
);
|
||||
|
||||
// Section order from store
|
||||
const setProjectOrder = useSectionOrderStore((state) => state.setProjectOrder);
|
||||
|
||||
const handleDragEnd = useCallback(
|
||||
(newData: SectionData[]) => {
|
||||
const newOrder = newData.map((section) => section.projectKey);
|
||||
setProjectOrder(newOrder);
|
||||
},
|
||||
[setProjectOrder]
|
||||
);
|
||||
|
||||
const handleArchiveAgent = useCallback(
|
||||
(e: { stopPropagation: () => void }, agent: AggregatedAgent) => {
|
||||
e.stopPropagation();
|
||||
const session = useSessionStore.getState().sessions[agent.serverId];
|
||||
const client = session?.client ?? null;
|
||||
if (client) {
|
||||
void client.archiveAgent(agent.id).catch((error) => {
|
||||
console.warn("[archive_agent] failed", error);
|
||||
});
|
||||
}
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const renderSection = useCallback(
|
||||
({ item: section, drag, isActive }: DraggableRenderItemInfo<SectionData>) => {
|
||||
const isCollapsed = collapsedProjectKeys.has(section.projectKey);
|
||||
const firstAgent = section.agents[0];
|
||||
const firstAgentKey = firstAgent
|
||||
? `${firstAgent.serverId}:${firstAgent.id}`
|
||||
: null;
|
||||
const firstAgentCheckout = firstAgentKey
|
||||
? (checkoutByAgentKey.get(firstAgentKey) ?? null)
|
||||
: null;
|
||||
|
||||
return (
|
||||
<View style={[styles.sectionContainer, isActive && styles.sectionDragging]}>
|
||||
<SectionHeader
|
||||
section={section}
|
||||
checkout={firstAgentCheckout}
|
||||
isCollapsed={isCollapsed}
|
||||
onToggle={() => toggleProjectCollapsed(section.projectKey)}
|
||||
onCreateAgent={handleCreateAgentInProject}
|
||||
onDrag={drag}
|
||||
isDragging={isActive}
|
||||
/>
|
||||
{!isCollapsed &&
|
||||
section.agents.map((agent) => (
|
||||
<SidebarAgentRow
|
||||
key={`${agent.serverId}:${agent.id}`}
|
||||
agent={agent}
|
||||
checkout={
|
||||
checkoutByAgentKey.get(`${agent.serverId}:${agent.id}`) ?? null
|
||||
}
|
||||
isSelected={selectedAgentId === `${agent.serverId}:${agent.id}`}
|
||||
shortcutNumber={
|
||||
showShortcutBadges
|
||||
? (shortcutIndexByAgentKey.get(`${agent.serverId}:${agent.id}`) ?? null)
|
||||
: null
|
||||
}
|
||||
onPress={() => handleAgentPress(agent.serverId, agent.id)}
|
||||
onLongPress={() => handleAgentLongPress(agent)}
|
||||
onArchive={(e) => handleArchiveAgent(e, agent)}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
);
|
||||
},
|
||||
[
|
||||
collapsedProjectKeys,
|
||||
handleAgentLongPress,
|
||||
handleAgentPress,
|
||||
handleArchiveAgent,
|
||||
handleCreateAgentInProject,
|
||||
checkoutByAgentKey,
|
||||
selectedAgentId,
|
||||
showShortcutBadges,
|
||||
shortcutIndexByAgentKey,
|
||||
toggleProjectCollapsed,
|
||||
]
|
||||
);
|
||||
|
||||
const keyExtractor = useCallback(
|
||||
(section: SectionData) => section.key,
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<DraggableList
|
||||
data={sections}
|
||||
style={styles.list}
|
||||
contentContainerStyle={styles.listContent}
|
||||
keyExtractor={keyExtractor}
|
||||
renderItem={renderSection}
|
||||
onDragEnd={handleDragEnd}
|
||||
showsVerticalScrollIndicator={false}
|
||||
ListFooterComponent={listFooterComponent}
|
||||
refreshing={isRefreshing}
|
||||
onRefresh={onRefresh}
|
||||
simultaneousGestureRef={parentGestureRef}
|
||||
/>
|
||||
|
||||
<Modal
|
||||
visible={isActionSheetVisible}
|
||||
animationType="fade"
|
||||
transparent
|
||||
onRequestClose={handleCloseActionSheet}
|
||||
>
|
||||
<View style={styles.sheetOverlay}>
|
||||
<Pressable
|
||||
style={styles.sheetBackdrop}
|
||||
onPress={handleCloseActionSheet}
|
||||
/>
|
||||
<View style={[styles.sheetContainer, { paddingBottom: Math.max(insets.bottom, theme.spacing[6]) }]}>
|
||||
<View style={styles.sheetHandle} />
|
||||
<Text style={styles.sheetTitle}>
|
||||
{isActionDaemonUnavailable
|
||||
? "Host offline"
|
||||
: "Archive this agent?"}
|
||||
</Text>
|
||||
<View style={styles.sheetButtonRow}>
|
||||
<Pressable
|
||||
style={[styles.sheetButton, styles.sheetCancelButton]}
|
||||
onPress={handleCloseActionSheet}
|
||||
testID="agent-action-cancel"
|
||||
>
|
||||
<Text style={styles.sheetCancelText}>Cancel</Text>
|
||||
</Pressable>
|
||||
<Pressable
|
||||
disabled={isActionDaemonUnavailable}
|
||||
style={[styles.sheetButton, styles.sheetArchiveButton]}
|
||||
onPress={handleArchiveFromSheet}
|
||||
testID="agent-action-archive"
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
styles.sheetArchiveText,
|
||||
isActionDaemonUnavailable && styles.sheetArchiveTextDisabled,
|
||||
]}
|
||||
>
|
||||
Archive
|
||||
</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create((theme) => ({
|
||||
list: {
|
||||
flex: 1,
|
||||
},
|
||||
listContent: {
|
||||
paddingTop: theme.spacing[2],
|
||||
paddingBottom: theme.spacing[4],
|
||||
},
|
||||
sectionHeader: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
paddingVertical: theme.spacing[2],
|
||||
paddingHorizontal: theme.spacing[3],
|
||||
marginTop: theme.spacing[2],
|
||||
borderRadius: theme.borderRadius.md,
|
||||
},
|
||||
sectionHeaderHovered: {
|
||||
backgroundColor: theme.colors.surface1,
|
||||
},
|
||||
sectionHeaderPressed: {
|
||||
backgroundColor: theme.colors.surface2,
|
||||
},
|
||||
sectionHeaderExpanded: {
|
||||
marginBottom: theme.spacing[2],
|
||||
},
|
||||
sectionHeaderDragging: {
|
||||
backgroundColor: theme.colors.surface2,
|
||||
},
|
||||
sectionContainer: {
|
||||
marginHorizontal: theme.spacing[2],
|
||||
marginBottom: theme.spacing[2],
|
||||
},
|
||||
sectionDragging: {
|
||||
opacity: 0.9,
|
||||
transform: [{ scale: 1.02 }],
|
||||
},
|
||||
chevron: {
|
||||
opacity: 0.5,
|
||||
},
|
||||
sectionHeaderLeft: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-start",
|
||||
flex: 1,
|
||||
minWidth: 0,
|
||||
gap: theme.spacing[2],
|
||||
},
|
||||
projectIcon: {
|
||||
width: 16,
|
||||
height: 16,
|
||||
borderRadius: theme.borderRadius.sm,
|
||||
},
|
||||
projectIconPlaceholder: {
|
||||
width: 16,
|
||||
height: 16,
|
||||
borderRadius: theme.borderRadius.sm,
|
||||
borderWidth: 1,
|
||||
borderColor: theme.colors.border,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
},
|
||||
projectIconPlaceholderText: {
|
||||
fontSize: 10,
|
||||
fontWeight: "500",
|
||||
color: theme.colors.foregroundMuted,
|
||||
},
|
||||
sectionHeaderRight: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
gap: theme.spacing[2],
|
||||
marginLeft: theme.spacing[2],
|
||||
},
|
||||
createAgentButton: {
|
||||
width: 18,
|
||||
height: 18,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
},
|
||||
sectionTitle: {
|
||||
fontSize: theme.fontSize.sm,
|
||||
fontWeight: "normal",
|
||||
color: theme.colors.foregroundMuted,
|
||||
flex: 1,
|
||||
textAlign: "left",
|
||||
},
|
||||
agentItem: {
|
||||
paddingVertical: theme.spacing[2],
|
||||
paddingHorizontal: theme.spacing[3],
|
||||
marginLeft: theme.spacing[1],
|
||||
borderRadius: theme.borderRadius.lg,
|
||||
marginBottom: theme.spacing[1],
|
||||
},
|
||||
agentItemUnselected: {
|
||||
opacity: 0.75,
|
||||
},
|
||||
agentItemSelected: {
|
||||
backgroundColor: theme.colors.surface2,
|
||||
},
|
||||
agentItemHovered: {
|
||||
backgroundColor: theme.colors.surface1,
|
||||
},
|
||||
agentItemPressed: {
|
||||
backgroundColor: theme.colors.surface2,
|
||||
},
|
||||
agentContent: {
|
||||
flex: 1,
|
||||
gap: theme.spacing[0],
|
||||
},
|
||||
row: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: theme.spacing[2],
|
||||
},
|
||||
agentTitle: {
|
||||
flex: 1,
|
||||
fontSize: theme.fontSize.base,
|
||||
fontWeight: "400",
|
||||
color: theme.colors.foreground,
|
||||
opacity: 0.8,
|
||||
},
|
||||
archiveButton: {
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
marginLeft: theme.spacing[1],
|
||||
},
|
||||
branchBadge: {
|
||||
minWidth: 20,
|
||||
maxWidth: "40%",
|
||||
height: 20,
|
||||
paddingHorizontal: theme.spacing[2],
|
||||
borderRadius: theme.borderRadius.md,
|
||||
borderWidth: 1,
|
||||
borderColor: theme.colors.border,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
},
|
||||
branchBadgeText: {
|
||||
fontSize: theme.fontSize.xs,
|
||||
color: theme.colors.foregroundMuted,
|
||||
lineHeight: 12,
|
||||
},
|
||||
archiveConfirmBadge: {
|
||||
minWidth: 76,
|
||||
maxWidth: 9999,
|
||||
flexShrink: 0,
|
||||
},
|
||||
archiveConfirmText: {
|
||||
color: theme.colors.foreground,
|
||||
},
|
||||
archiveConfirmTextHovered: {
|
||||
opacity: 0.9,
|
||||
},
|
||||
agentTitleHighlighted: {
|
||||
color: theme.colors.foreground,
|
||||
opacity: 1,
|
||||
},
|
||||
secondaryRow: {
|
||||
fontSize: theme.fontSize.sm,
|
||||
fontWeight: "300",
|
||||
color: theme.colors.foregroundMuted,
|
||||
},
|
||||
sheetOverlay: {
|
||||
flex: 1,
|
||||
justifyContent: "flex-end",
|
||||
},
|
||||
sheetBackdrop: {
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
backgroundColor: "rgba(0,0,0,0.35)",
|
||||
},
|
||||
sheetContainer: {
|
||||
backgroundColor: theme.colors.surface2,
|
||||
borderTopLeftRadius: theme.borderRadius["2xl"],
|
||||
borderTopRightRadius: theme.borderRadius["2xl"],
|
||||
paddingHorizontal: theme.spacing[6],
|
||||
paddingTop: theme.spacing[4],
|
||||
gap: theme.spacing[4],
|
||||
},
|
||||
sheetHandle: {
|
||||
alignSelf: "center",
|
||||
width: 40,
|
||||
height: 4,
|
||||
borderRadius: theme.borderRadius.full,
|
||||
backgroundColor: theme.colors.foregroundMuted,
|
||||
opacity: 0.3,
|
||||
},
|
||||
sheetTitle: {
|
||||
fontSize: theme.fontSize.lg,
|
||||
fontWeight: theme.fontWeight.semibold,
|
||||
color: theme.colors.foreground,
|
||||
textAlign: "center",
|
||||
},
|
||||
sheetButtonRow: {
|
||||
flexDirection: "row",
|
||||
gap: theme.spacing[3],
|
||||
},
|
||||
sheetButton: {
|
||||
flex: 1,
|
||||
borderRadius: theme.borderRadius.lg,
|
||||
paddingVertical: theme.spacing[4],
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
},
|
||||
sheetArchiveButton: {
|
||||
backgroundColor: theme.colors.primary,
|
||||
},
|
||||
sheetArchiveText: {
|
||||
color: theme.colors.primaryForeground,
|
||||
fontWeight: theme.fontWeight.semibold,
|
||||
fontSize: theme.fontSize.base,
|
||||
},
|
||||
sheetArchiveTextDisabled: {
|
||||
opacity: 0.5,
|
||||
},
|
||||
sheetCancelButton: {
|
||||
backgroundColor: theme.colors.surface1,
|
||||
},
|
||||
sheetCancelText: {
|
||||
color: theme.colors.foreground,
|
||||
fontWeight: theme.fontWeight.semibold,
|
||||
fontSize: theme.fontSize.base,
|
||||
},
|
||||
}));
|
||||
Reference in New Issue
Block a user