From 70a1852e5759741a6b32df62842b01c766a3f109 Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Sun, 26 Oct 2025 20:37:14 +0100 Subject: [PATCH] refactor(modal): replace bottom sheet with native modal and custom animations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace @gorhom/bottom-sheet with React Native's native Modal component and implement custom slide-in/slide-out animations using Reanimated. This provides better control over modal behavior and animations while reducing dependency on external libraries. Key changes: - Replace BottomSheetModal with native Modal component - Implement custom slide animations using Reanimated shared values - Add backdrop fade in/out animations - Extract modal sections into focused components (ModalHeader, WorkingDirectorySection, AssistantSelector, ModeSelector, WorktreeSection) - Add responsive layout support with compact/wide screen detection - Fix keyboard animation to use transform instead of padding - Improve modal lifecycle management with mount/unmount state tracking 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../app/src/components/create-agent-modal.tsx | 989 +++++++++++------- packages/app/src/components/global-footer.tsx | 5 +- packages/server/agents.json | 61 +- 3 files changed, 650 insertions(+), 405 deletions(-) diff --git a/packages/app/src/components/create-agent-modal.tsx b/packages/app/src/components/create-agent-modal.tsx index 0ec7ed6f4..0e2acea5b 100644 --- a/packages/app/src/components/create-agent-modal.tsx +++ b/packages/app/src/components/create-agent-modal.tsx @@ -6,22 +6,22 @@ import { ScrollView, ActivityIndicator, InteractionManager, + TextInput, + Modal, + useWindowDimensions, + type LayoutChangeEvent, } from "react-native"; import { useSafeAreaInsets } from "react-native-safe-area-context"; import { useReanimatedKeyboardAnimation } from "react-native-keyboard-controller"; -import Animated, { useAnimatedStyle } from "react-native-reanimated"; -import { - BottomSheetModal, - BottomSheetBackdrop, - BottomSheetScrollView, - BottomSheetTextInput, - BottomSheetFooter, -} from "@gorhom/bottom-sheet"; -import type { - BottomSheetBackdropProps, - BottomSheetFooterProps, -} from "@gorhom/bottom-sheet"; +import Animated, { + useAnimatedStyle, + useSharedValue, + withTiming, + Easing, + runOnJS, +} from "react-native-reanimated"; import { StyleSheet } from "react-native-unistyles"; +import { X } from "lucide-react-native"; import { theme as defaultTheme } from "@/styles/theme"; import { useRecentPaths } from "@/hooks/use-recent-paths"; import { useSession } from "@/contexts/session-context"; @@ -31,6 +31,7 @@ import { listAgentTypeDefinitions, type AgentType, type AgentTypeDefinition, + type AgentModeDefinition, } from "@server/server/acp/agent-types"; interface CreateAgentModalProps { @@ -50,30 +51,37 @@ const fallbackDefinition = agentTypeDefinitionMap.claude ?? agentTypeDefinitions const DEFAULT_AGENT_TYPE: AgentType = fallbackDefinition ? fallbackDefinition.id : "claude"; -const DEFAULT_MODE_FOR_DEFAULT_AGENT = fallbackDefinition?.defaultModeId; +const DEFAULT_MODE_FOR_DEFAULT_AGENT = fallbackDefinition?.defaultModeId ?? ""; +const BACKDROP_OPACITY = 0.55; export function CreateAgentModal({ isVisible, onClose, }: CreateAgentModalProps) { - const bottomSheetRef = useRef(null); const insets = useSafeAreaInsets(); - const { recentPaths, addRecentPath } = useRecentPaths(); + const { height: screenHeight, width: screenWidth } = useWindowDimensions(); + const slideOffset = useSharedValue(screenHeight); + const backdropOpacity = useSharedValue(0); const { height: keyboardHeight } = useReanimatedKeyboardAnimation(); + const isCompactLayout = screenWidth < 720; + + const { recentPaths, addRecentPath } = useRecentPaths(); const { ws, createAgent } = useSession(); const router = useRouter(); + const [isMounted, setIsMounted] = useState(isVisible); const [workingDir, setWorkingDir] = useState(""); const [selectedAgentType, setSelectedAgentType] = useState( DEFAULT_AGENT_TYPE ); - const [selectedMode, setSelectedMode] = useState( - DEFAULT_MODE_FOR_DEFAULT_AGENT ?? "" - ); + const [selectedMode, setSelectedMode] = useState(DEFAULT_MODE_FOR_DEFAULT_AGENT); const [worktreeName, setWorktreeName] = useState(""); const [errorMessage, setErrorMessage] = useState(""); const [isLoading, setIsLoading] = useState(false); const [pendingRequestId, setPendingRequestId] = useState(null); + + const pendingNavigationAgentIdRef = useRef(null); + const agentDefinition = agentTypeDefinitionMap[selectedAgentType]; const modeOptions = agentDefinition?.availableModes ?? []; @@ -82,53 +90,138 @@ export function CreateAgentModal({ return; } - const availableModeIds = agentDefinition.availableModes.map( - (mode) => mode.id - ); - - if (availableModeIds.length === 0) { + if (modeOptions.length === 0) { if (selectedMode !== "") { setSelectedMode(""); } return; } + const availableModeIds = modeOptions.map((mode) => mode.id); + if (!availableModeIds.includes(selectedMode)) { const fallbackModeId = agentDefinition.defaultModeId ?? availableModeIds[0]; setSelectedMode(fallbackModeId); } - }, [agentDefinition, selectedMode]); + }, [agentDefinition, selectedMode, modeOptions]); - // Use ref instead of state to survive state resets in onDismiss - const pendingNavigationAgentIdRef = useRef(null); + const resetFormState = useCallback(() => { + setWorkingDir(""); + setWorktreeName(""); + setSelectedAgentType(DEFAULT_AGENT_TYPE); + setSelectedMode(DEFAULT_MODE_FOR_DEFAULT_AGENT); + setErrorMessage(""); + setIsLoading(false); + setPendingRequestId(null); + }, []); - const snapPoints = useMemo(() => ["90%"], []); + const navigateToAgentIfNeeded = useCallback(() => { + const agentId = pendingNavigationAgentIdRef.current; + if (!agentId) { + return; + } - // Keyboard animation for footer - const animatedFooterStyle = useAnimatedStyle(() => { + pendingNavigationAgentIdRef.current = null; + InteractionManager.runAfterInteractions(() => { + router.push(`/agent/${agentId}`); + }); + }, [router]); + + const handleCloseAnimationComplete = useCallback(() => { + console.log("[CreateAgentModal] close animation complete – resetting form"); + resetFormState(); + setIsMounted(false); + navigateToAgentIfNeeded(); + }, [navigateToAgentIfNeeded, resetFormState]); + + useEffect(() => { + if (!isVisible) { + console.log("[CreateAgentModal] visibility effect skipped (isVisible is false)", { + isMounted, + }); + return; + } + + console.log("[CreateAgentModal] visibility effect triggered", { + wasMounted: isMounted, + screenHeight, + }); + setIsMounted(true); + slideOffset.value = screenHeight; + backdropOpacity.value = 0; + + backdropOpacity.value = withTiming(BACKDROP_OPACITY, { + duration: 200, + easing: Easing.out(Easing.cubic), + }); + slideOffset.value = withTiming(0, { + duration: 240, + easing: Easing.out(Easing.cubic), + }); + }, [isVisible, slideOffset, backdropOpacity, screenHeight]); + + useEffect(() => { + if (!isMounted || isVisible) { + console.log("[CreateAgentModal] close animation skipped", { + isMounted, + isVisible, + }); + return; + } + + console.log("[CreateAgentModal] close animation starting", { + screenHeight, + }); + backdropOpacity.value = withTiming(0, { + duration: 160, + easing: Easing.out(Easing.cubic), + }); + slideOffset.value = withTiming( + screenHeight, + { + duration: 220, + easing: Easing.in(Easing.cubic), + }, + (finished) => { + if (finished) { + console.log("[CreateAgentModal] slide animation finished"); + runOnJS(handleCloseAnimationComplete)(); + } + } + ); + }, [ + isMounted, + isVisible, + slideOffset, + backdropOpacity, + screenHeight, + handleCloseAnimationComplete, + ]); + + const footerAnimatedStyle = useAnimatedStyle(() => { "worklet"; const absoluteHeight = Math.abs(keyboardHeight.value); - const padding = Math.max(0, absoluteHeight - insets.bottom); + const shift = Math.max(0, absoluteHeight - insets.bottom); return { - paddingBottom: padding, + transform: [{ translateY: -shift }], }; - }); + }, [insets.bottom, keyboardHeight]); - const renderBackdrop = useMemo( - () => (props: BottomSheetBackdropProps) => - ( - - ), - [] - ); + const containerAnimatedStyle = useAnimatedStyle(() => { + "worklet"; + return { + transform: [{ translateY: slideOffset.value }], + }; + }, [slideOffset]); + + const backdropAnimatedStyle = useAnimatedStyle(() => { + "worklet"; + return { + opacity: backdropOpacity.value, + }; + }, [backdropOpacity]); - // Slugify helper function const slugifyWorktreeName = useCallback((input: string): string => { return input .toLowerCase() @@ -136,16 +229,16 @@ export function CreateAgentModal({ .replace(/^-+|-+$/g, ""); }, []); - // Validate worktree name const validateWorktreeName = useCallback((name: string): { valid: boolean; error?: string } => { - if (!name) return { valid: true }; // Optional field + if (!name) { + return { valid: true }; + } if (name.length > 100) { return { valid: false, error: "Worktree name too long (max 100 characters)" }; } - const validPattern = /^[a-z0-9-]+$/; - if (!validPattern.test(name)) { + if (!/^[a-z0-9-]+$/.test(name)) { return { valid: false, error: "Must contain only lowercase letters, numbers, and hyphens", @@ -163,8 +256,13 @@ export function CreateAgentModal({ return { valid: true }; }, []); + const handleClose = useCallback(() => { + onClose(); + }, [onClose]); + const handleCreate = useCallback(async () => { - if (!workingDir.trim()) { + const trimmedPath = workingDir.trim(); + if (!trimmedPath) { setErrorMessage("Working directory is required"); return; } @@ -173,10 +271,8 @@ export function CreateAgentModal({ return; } - const path = workingDir.trim(); const worktree = worktreeName.trim(); - // Validate worktree name if provided if (worktree) { const validation = validateWorktreeName(worktree); if (!validation.valid) { @@ -185,15 +281,12 @@ export function CreateAgentModal({ } } - // Save to recent paths try { - await addRecentPath(path); + await addRecentPath(trimmedPath); } catch (error) { console.error("[CreateAgentModal] Failed to save recent path:", error); - // Continue anyway - don't block agent creation } - // Generate request ID const requestId = generateMessageId(); setIsLoading(true); @@ -203,10 +296,9 @@ export function CreateAgentModal({ const modeId = modeOptions.length > 0 && selectedMode !== "" ? selectedMode : undefined; - // Create the agent try { createAgent({ - cwd: path, + cwd: trimmedPath, agentType: selectedAgentType, initialMode: modeId, worktreeName: worktree || undefined, @@ -230,61 +322,23 @@ export function CreateAgentModal({ createAgent, ]); - const renderFooter = useCallback( - (props: BottomSheetFooterProps) => ( - - - - {isLoading ? ( - - - Creating... - - ) : ( - Create Agent - )} - - - - ), - [insets.bottom, workingDir, animatedFooterStyle, isLoading, handleCreate] - ); - useEffect(() => { - if (isVisible) { - bottomSheetRef.current?.present(); + if (!pendingRequestId) { + return; } - }, [isVisible]); - - // Listen for agent_created events - useEffect(() => { - if (!pendingRequestId) return; const unsubscribe = ws.on("agent_created", (message) => { - if (message.type !== "agent_created") return; + if (message.type !== "agent_created") { + return; + } const { agentId, requestId } = message.payload; - // Check if this is the response to our request if (requestId === pendingRequestId) { console.log("[CreateAgentModal] Agent created:", agentId); setIsLoading(false); setPendingRequestId(null); - - // Store the agent ID in ref for navigation after modal dismisses - // Using ref instead of state because state gets reset in onDismiss pendingNavigationAgentIdRef.current = agentId; - - // Close modal - navigation will happen in handleDismiss handleClose(); } }); @@ -292,314 +346,508 @@ export function CreateAgentModal({ return () => { unsubscribe(); }; - }, [pendingRequestId, ws]); + }, [pendingRequestId, ws, handleClose]); - function handleClose() { - bottomSheetRef.current?.dismiss(); + const shouldRender = isVisible || isMounted; + + const workingDirIsEmpty = !workingDir.trim(); + const headerPaddingTop = useMemo(() => insets.top + defaultTheme.spacing[4], [insets.top]); + const horizontalPaddingLeft = useMemo(() => defaultTheme.spacing[6] + insets.left, [insets.left]); + const horizontalPaddingRight = useMemo(() => defaultTheme.spacing[6] + insets.right, [insets.right]); + + const handleSheetLayout = useCallback((event: LayoutChangeEvent) => { + const { height, y } = event.nativeEvent.layout; + console.log("[CreateAgentModal] sheet layout", { height, y }); + }, []); + + if (!shouldRender) { + console.log("[CreateAgentModal] render skipped", { + isVisible, + isMounted, + }); + return null; } - function handleDismiss() { - const agentId = pendingNavigationAgentIdRef.current; - - // Reset all state - setWorkingDir(""); - setWorktreeName(""); - setSelectedAgentType(DEFAULT_AGENT_TYPE); - setSelectedMode(DEFAULT_MODE_FOR_DEFAULT_AGENT ?? ""); - setErrorMessage(""); - setIsLoading(false); - setPendingRequestId(null); - onClose(); - - // Navigate after interactions complete to avoid race with dismiss animation - if (agentId) { - InteractionManager.runAfterInteractions(() => { - router.push(`/agent/${agentId}`); - pendingNavigationAgentIdRef.current = null; - }); - } - } + console.log("[CreateAgentModal] rendering modal", { + isVisible, + isMounted, + }); return ( - - - {/* Header */} - - Create New Agent - - - {/* Form */} - {/* Working Directory Input */} - - Working Directory - { - setWorkingDir(text); - setErrorMessage(""); - }} - autoCapitalize="none" - autoCorrect={false} - editable={!isLoading} + + + + + + + - {errorMessage && ( - {errorMessage} - )} + + { + setWorkingDir(value); + setErrorMessage(""); + }} + /> - {/* Recent Paths Chips */} - {recentPaths.length > 0 && ( - - {recentPaths.map((path) => ( - setWorkingDir(path)} - > - - {path} - - - ))} - - )} - + + + - {/* Agent Type Selector */} - - Agent Type - - {agentTypeDefinitions.map((definition) => { - const isSelected = selectedAgentType === definition.id; - return ( - setSelectedAgentType(definition.id)} - disabled={isLoading} + { + const slugified = slugifyWorktreeName(text); + setWorktreeName(slugified); + setErrorMessage(""); + }} + /> + + + + + {isLoading ? ( + + + Creating... + + ) : ( + Create Agent + )} + + + + + + + ); +} + +interface ModalHeaderProps { + paddingTop: number; + paddingLeft: number; + paddingRight: number; + onClose: () => void; +} + +function ModalHeader({ paddingTop, paddingLeft, paddingRight, onClose }: ModalHeaderProps): JSX.Element { + return ( + + Create New Agent + + + + + ); +} + +interface WorkingDirectorySectionProps { + workingDir: string; + isLoading: boolean; + errorMessage: string; + recentPaths: string[]; + onChangeWorkingDir: (value: string) => void; +} + +function WorkingDirectorySection({ + workingDir, + isLoading, + errorMessage, + recentPaths, + onChangeWorkingDir, +}: WorkingDirectorySectionProps): JSX.Element { + return ( + + Working Directory + + {errorMessage ? {errorMessage} : null} + {recentPaths.length > 0 ? ( + + {recentPaths.map((path) => ( + onChangeWorkingDir(path)}> + + {path} + + + ))} + + ) : null} + + ); +} + +interface AssistantSelectorProps { + agentTypeDefinitions: AgentTypeDefinition[]; + selectedAgentType: AgentType; + disabled: boolean; + isStacked: boolean; + onSelect: (agentType: AgentType) => void; +} + +function AssistantSelector({ + agentTypeDefinitions, + selectedAgentType, + disabled, + isStacked, + onSelect, +}: AssistantSelectorProps): JSX.Element { + return ( + + Assistant + + {agentTypeDefinitions.map((definition) => { + const isSelected = selectedAgentType === definition.id; + return ( + onSelect(definition.id)} + disabled={disabled} + style={[ + styles.optionCard, + isSelected && styles.optionCardSelected, + disabled && styles.optionCardDisabled, + ]} + > + + + {isSelected ? : null} + + {definition.label} + + + ); + })} + + + ); +} + +interface ModeSelectorProps { + modeOptions: AgentModeDefinition[]; + selectedMode: string; + disabled: boolean; + isStacked: boolean; + onSelect: (modeId: string) => void; +} + +function ModeSelector({ + modeOptions, + selectedMode, + disabled, + isStacked, + onSelect, +}: ModeSelectorProps): JSX.Element { + return ( + + Permissions + {modeOptions.length === 0 ? ( + This assistant does not expose selectable permissions. + ) : ( + + {modeOptions.map((mode) => { + const isSelected = selectedMode === mode.id; + return ( + onSelect(mode.id)} + disabled={disabled} + style={[ + styles.optionCard, + isSelected && styles.optionCardSelected, + disabled && styles.optionCardDisabled, + ]} + > + + - - - {isSelected && } - - - - {definition.label} - - {definition.description ? ( - - {definition.description} - - ) : null} - - {definition.availableModes.length > 0 - ? `Modes: ${definition.availableModes - .map((mode) => mode.name) - .join(", ")}` - : "Modes: none"} - - - - - ); - })} - - + {isSelected ? : null} + + + {mode.name} + {mode.description ? {mode.description} : null} + + + + ); + })} + + )} + + ); +} - {/* Worktree Name Input (Optional) */} - - Worktree Name (Optional) - { - // Auto-slugify as user types - const slugified = slugifyWorktreeName(text); - setWorktreeName(slugified); - setErrorMessage(""); - }} - autoCapitalize="none" - autoCorrect={false} - editable={!isLoading} - /> - - Create a git worktree for isolated development. Must be lowercase, alphanumeric, and hyphens only. - - +interface WorktreeSectionProps { + value: string; + isLoading: boolean; + onChange: (value: string) => void; +} - {/* Mode Selector */} - - Mode - {modeOptions.length === 0 ? ( - - This agent type does not expose selectable modes. - - ) : ( - - {modeOptions.map((mode) => { - const isSelected = selectedMode === mode.id; - return ( - setSelectedMode(mode.id)} - disabled={isLoading} - style={[ - styles.modeOption, - isSelected && styles.modeOptionSelected, - isLoading && styles.modeOptionDisabled, - ]} - > - - - {isSelected && } - - - {mode.name} - {mode.description ? ( - - {mode.description} - - ) : null} - - - - ); - })} - - )} - - - +function WorktreeSection({ value, isLoading, onChange }: WorktreeSectionProps): JSX.Element { + return ( + + Worktree Name (Optional) + + + Create a git worktree for isolated development. Must be lowercase, alphanumeric, and hyphens only. + + ); } const styles = StyleSheet.create((theme) => ({ - sheetBackground: { - backgroundColor: theme.colors.card, + overlay: { + flex: 1, + justifyContent: "flex-end", }, - handleIndicator: { - backgroundColor: theme.colors.border, + backdrop: { + position: "absolute", + top: 0, + right: 0, + bottom: 0, + left: 0, + backgroundColor: theme.colors.palette.gray[900], + zIndex: 1, + }, + backdropPressable: { + flex: 1, + }, + sheet: { + position: "absolute", + left: 0, + right: 0, + top: 0, + bottom: 0, + zIndex: 2, + width: "100%", + backgroundColor: theme.colors.card, + overflow: "hidden", + }, + content: { + flex: 1, }, header: { - alignItems: "center", - justifyContent: "center", - padding: theme.spacing[6], + paddingBottom: theme.spacing[4], borderBottomWidth: theme.borderWidth[1], borderBottomColor: theme.colors.border, + flexDirection: "row", + alignItems: "center", + justifyContent: "space-between", }, headerTitle: { color: theme.colors.foreground, - fontSize: theme.fontSize.lg, + fontSize: theme.fontSize["2xl"], fontWeight: theme.fontWeight.semibold, }, + closeButton: { + padding: theme.spacing[2], + borderRadius: theme.borderRadius.full, + alignItems: "center", + justifyContent: "center", + }, + closeIcon: { + color: theme.colors.mutedForeground, + }, + scroll: { + flex: 1, + }, scrollContent: { - padding: theme.spacing[6], - // Add extra padding at bottom to account for fixed footer button - // Footer height is roughly: padding (16) + button (48) + margin (16) = 80 - paddingBottom: 100, + paddingTop: theme.spacing[6], + paddingBottom: theme.spacing[8], + gap: theme.spacing[6], }, formSection: { - marginBottom: theme.spacing[6], + gap: theme.spacing[3], }, label: { color: theme.colors.foreground, - fontSize: theme.fontSize.base, + fontSize: theme.fontSize.sm, fontWeight: theme.fontWeight.semibold, - marginBottom: theme.spacing[2], }, input: { backgroundColor: theme.colors.background, - color: theme.colors.foreground, - padding: theme.spacing[4], - borderRadius: theme.borderRadius.lg, borderWidth: theme.borderWidth[1], borderColor: theme.colors.border, + borderRadius: theme.borderRadius.lg, + paddingVertical: theme.spacing[3], + paddingHorizontal: theme.spacing[4], + color: theme.colors.foreground, fontSize: theme.fontSize.base, }, inputDisabled: { opacity: theme.opacity[50], }, + errorText: { + color: theme.colors.palette.red[500], + fontSize: theme.fontSize.sm, + }, helperText: { color: theme.colors.mutedForeground, fontSize: theme.fontSize.sm, - marginTop: theme.spacing[2], }, - errorText: { - color: theme.colors.destructive, + recentPathsContainer: { + flexDirection: "row", + gap: theme.spacing[2], + paddingVertical: theme.spacing[2], + }, + recentPathChip: { + backgroundColor: theme.colors.muted, + borderRadius: theme.borderRadius.full, + paddingHorizontal: theme.spacing[4], + paddingVertical: theme.spacing[2], + borderWidth: theme.borderWidth[1], + borderColor: theme.colors.border, + maxWidth: 200, + }, + recentPathChipText: { + color: theme.colors.foreground, fontSize: theme.fontSize.sm, - marginTop: theme.spacing[2], + fontWeight: theme.fontWeight.semibold, }, - modeContainer: { + selectorRow: { + flexDirection: "row", + gap: theme.spacing[4], + }, + selectorRowStacked: { + flexDirection: "column", + }, + selectorColumn: { + flex: 1, gap: theme.spacing[3], }, - modeOption: { + selectorColumnFull: { + width: "100%", + }, + optionGroup: { + gap: theme.spacing[3], + }, + optionCard: { backgroundColor: theme.colors.background, borderRadius: theme.borderRadius.lg, borderWidth: theme.borderWidth[1], borderColor: theme.colors.border, - padding: theme.spacing[4], + paddingHorizontal: theme.spacing[4], + paddingVertical: theme.spacing[3], }, - modeOptionSelected: { + optionCardSelected: { borderColor: theme.colors.palette.blue[500], backgroundColor: theme.colors.muted, }, - modeOptionDisabled: { + optionCardDisabled: { opacity: theme.opacity[50], }, - modeOptionContent: { + optionContent: { flexDirection: "row", alignItems: "center", + gap: theme.spacing[3], + }, + optionLabel: { + color: theme.colors.foreground, + fontSize: theme.fontSize.base, + fontWeight: theme.fontWeight.semibold, }, radioOuter: { width: 20, height: 20, borderRadius: theme.borderRadius.full, borderWidth: theme.borderWidth[2], - marginRight: theme.spacing[3], alignItems: "center", justifyContent: "center", }, @@ -617,60 +865,15 @@ const styles = StyleSheet.create((theme) => ({ }, modeTextContainer: { flex: 1, - }, - modeLabel: { - color: theme.colors.foreground, - fontSize: theme.fontSize.base, - fontWeight: theme.fontWeight.semibold, - marginBottom: theme.spacing[1], + gap: theme.spacing[1], }, modeDescription: { color: theme.colors.mutedForeground, fontSize: theme.fontSize.sm, }, - agentTypeContainer: { - gap: theme.spacing[3], - }, - agentTypeOption: { - backgroundColor: theme.colors.background, - borderRadius: theme.borderRadius.lg, - borderWidth: theme.borderWidth[1], - borderColor: theme.colors.border, - padding: theme.spacing[4], - }, - agentTypeOptionSelected: { - borderColor: theme.colors.palette.blue[500], - backgroundColor: theme.colors.muted, - }, - agentTypeOptionDisabled: { - opacity: theme.opacity[50], - }, - agentTypeOptionContent: { - flexDirection: "row", - alignItems: "center", - }, - agentTypeTextContainer: { - flex: 1, - }, - agentTypeLabel: { - color: theme.colors.foreground, - fontSize: theme.fontSize.base, - fontWeight: theme.fontWeight.semibold, - marginBottom: theme.spacing[1], - }, - agentTypeDescription: { - color: theme.colors.mutedForeground, - fontSize: theme.fontSize.sm, - marginBottom: theme.spacing[1], - }, - agentTypeMeta: { - color: theme.colors.mutedForeground, - fontSize: theme.fontSize.sm, - }, footer: { borderTopWidth: theme.borderWidth[1], borderTopColor: theme.colors.border, - paddingHorizontal: theme.spacing[6], paddingTop: theme.spacing[4], backgroundColor: theme.colors.card, }, @@ -678,7 +881,6 @@ const styles = StyleSheet.create((theme) => ({ backgroundColor: theme.colors.palette.blue[500], paddingVertical: theme.spacing[4], borderRadius: theme.borderRadius.lg, - marginBottom: theme.spacing[4], alignItems: "center", }, createButtonDisabled: { @@ -695,23 +897,4 @@ const styles = StyleSheet.create((theme) => ({ alignItems: "center", gap: theme.spacing[2], }, - recentPathsContainer: { - flexDirection: "row", - gap: theme.spacing[2], - paddingVertical: theme.spacing[3], - }, - recentPathChip: { - backgroundColor: theme.colors.muted, - borderRadius: theme.borderRadius.full, - paddingHorizontal: theme.spacing[4], - paddingVertical: theme.spacing[2], - borderWidth: theme.borderWidth[1], - borderColor: theme.colors.border, - maxWidth: 200, - }, - recentPathChipText: { - color: theme.colors.foreground, - fontSize: theme.fontSize.sm, - fontWeight: theme.fontWeight.semibold, - }, })); diff --git a/packages/app/src/components/global-footer.tsx b/packages/app/src/components/global-footer.tsx index 18b3783a3..2e78e81b7 100644 --- a/packages/app/src/components/global-footer.tsx +++ b/packages/app/src/components/global-footer.tsx @@ -110,7 +110,10 @@ export function GlobalFooter() { setShowCreateModal(true)} + onPress={() => { + console.log("[GlobalFooter] New Agent button pressed"); + setShowCreateModal(true); + }} style={({ pressed }) => [ styles.footerButton, pressed && styles.buttonPressed, diff --git a/packages/server/agents.json b/packages/server/agents.json index 76d15eb7c..c1df17073 100644 --- a/packages/server/agents.json +++ b/packages/server/agents.json @@ -386,12 +386,71 @@ { "id": "f1df26aa-4b20-499e-a84b-1d61828cf7fe", "title": "Prepare Concise Session Title", - "sessionId": "019a215a-22dd-7930-9db7-e38d256ac597", + "sessionId": "019a2187-dd32-7563-8622-92595939ad89", "options": { "type": "codex" }, "createdAt": "2025-10-26T16:29:01.354Z", "lastActivityAt": "2025-10-26T16:29:17.523Z", "cwd": "/Users/moboudra/dev/voice-dev" + }, + { + "id": "de1f30f2-9d27-4404-9f71-098c5adc04ec", + "title": "Create Agent Modal UI Upgrades", + "sessionId": "019a21a1-6568-7252-90e6-a7da2cc0f27d", + "options": { + "type": "codex" + }, + "createdAt": "2025-10-26T17:46:54.882Z", + "lastActivityAt": "2025-10-26T17:48:55.022Z", + "cwd": "/Users/moboudra/dev/voice-dev" + }, + { + "id": "94c0e1c6-3a7e-493d-895f-b178369f978a", + "title": "Fix Create Agent Modal Layout", + "sessionId": "dbee3818-e273-42fb-bf31-27fa46b2a25c", + "options": { + "type": "claude", + "sessionId": "dbee3818-e273-42fb-bf31-27fa46b2a25c" + }, + "createdAt": "2025-10-26T18:52:14.017Z", + "lastActivityAt": "2025-10-26T18:53:54.752Z", + "cwd": "/Users/moboudra/dev/voice-dev" + }, + { + "id": "cc4c74db-6269-43ce-b48b-236565bb857c", + "title": "Greeting Interaction in BlankPage Editor", + "sessionId": "82cdf496-2191-4416-b0a8-b16901cfe277", + "options": { + "type": "claude", + "sessionId": "82cdf496-2191-4416-b0a8-b16901cfe277" + }, + "createdAt": "2025-10-26T18:57:45.992Z", + "lastActivityAt": "2025-10-26T18:57:55.860Z", + "cwd": "/Users/moboudra/dev/blankpage/editor" + }, + { + "id": "daf7e31e-95f2-4ec9-a7cc-113b33e3a487", + "title": "Add Projects with File Browsing", + "sessionId": "5ba144c8-027e-41c1-80af-4b5a241db88d", + "options": { + "type": "claude", + "sessionId": "5ba144c8-027e-41c1-80af-4b5a241db88d" + }, + "createdAt": "2025-10-26T19:05:05.128Z", + "lastActivityAt": "2025-10-26T19:07:24.165Z", + "cwd": "/Users/moboudra/dev/voice-dev" + }, + { + "id": "090e938a-d9a8-4b2e-9fd9-70b6dfc87ad4", + "title": "Commit to Voice Dev Repo", + "sessionId": "019a2205-e54b-748f-bd06-eac1e168e84f", + "options": { + "type": "claude", + "sessionId": "2a933d01-f806-42cb-8b9d-8000b864900f" + }, + "createdAt": "2025-10-26T19:36:42.420Z", + "lastActivityAt": "2025-10-26T19:36:59.561Z", + "cwd": "/Users/moboudra/dev/voice-dev" } ] \ No newline at end of file