diff --git a/packages/app/app.json b/packages/app/app.json index 626f76be4..227587664 100644 --- a/packages/app/app.json +++ b/packages/app/app.json @@ -24,9 +24,7 @@ "android": { "adaptiveIcon": { "backgroundColor": "#E6F4FE", - "foregroundImage": "./assets/images/android-icon-foreground.png", - "backgroundImage": "./assets/images/android-icon-background.png", - "monochromeImage": "./assets/images/android-icon-monochrome.png" + "foregroundImage": "./assets/images/android-icon-foreground.png" }, "edgeToEdgeEnabled": true, "predictiveBackGestureEnabled": false, diff --git a/packages/app/assets/images/android-icon-foreground-backup.png b/packages/app/assets/images/android-icon-foreground-backup.png new file mode 100644 index 000000000..ae5077dee Binary files /dev/null and b/packages/app/assets/images/android-icon-foreground-backup.png differ diff --git a/packages/app/assets/images/android-icon-foreground.png b/packages/app/assets/images/android-icon-foreground.png index 27c94262e..d5fe93ef4 100644 Binary files a/packages/app/assets/images/android-icon-foreground.png and b/packages/app/assets/images/android-icon-foreground.png differ diff --git a/packages/app/assets/images/splash-icon.png b/packages/app/assets/images/splash-icon.png index 03d6f6b6c..ae5077dee 100644 Binary files a/packages/app/assets/images/splash-icon.png and b/packages/app/assets/images/splash-icon.png differ diff --git a/packages/app/src/components/create-agent-modal.tsx b/packages/app/src/components/create-agent-modal.tsx index be2c31650..e58706356 100644 --- a/packages/app/src/components/create-agent-modal.tsx +++ b/packages/app/src/components/create-agent-modal.tsx @@ -13,6 +13,7 @@ import { useSafeAreaInsets } from "react-native-safe-area-context"; import { X } from "lucide-react-native"; import { StyleSheet } from "react-native-unistyles"; import { theme as defaultTheme } from "@/styles/theme"; +import { useRecentPaths } from "@/hooks/use-recent-paths"; interface CreateAgentModalProps { isVisible: boolean; @@ -40,6 +41,7 @@ export function CreateAgentModal({ }: CreateAgentModalProps) { const insets = useSafeAreaInsets(); const { height: keyboardHeight } = useReanimatedKeyboardAnimation(); + const { recentPaths, addRecentPath } = useRecentPaths(); const [workingDir, setWorkingDir] = useState(""); const [selectedMode, setSelectedMode] = useState("plan"); @@ -56,13 +58,23 @@ export function CreateAgentModal({ }; }); - function handleCreate() { + async function handleCreate() { if (!workingDir.trim()) { setErrorMessage("Working directory is required"); return; } - onCreateAgent(workingDir.trim(), selectedMode); + const path = workingDir.trim(); + + // Save to recent paths + try { + await addRecentPath(path); + } catch (error) { + console.error("[CreateAgentModal] Failed to save recent path:", error); + // Continue anyway - don't block agent creation + } + + onCreateAgent(path, selectedMode); handleClose(); } @@ -123,6 +135,28 @@ export function CreateAgentModal({ Absolute path to the project directory )} + + {/* Recent Paths Chips */} + {recentPaths.length > 0 && ( + + {recentPaths.map((path) => ( + setWorkingDir(path)} + > + + {path} + + + ))} + + )} {/* Mode Selector */} @@ -339,4 +373,23 @@ const styles = StyleSheet.create((theme) => ({ fontSize: theme.fontSize.base, fontWeight: theme.fontWeight.semibold, }, + 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.medium, + }, })); diff --git a/packages/app/src/components/empty-state.tsx b/packages/app/src/components/empty-state.tsx index 361ebc3c7..f1a135f33 100644 --- a/packages/app/src/components/empty-state.tsx +++ b/packages/app/src/components/empty-state.tsx @@ -12,11 +12,9 @@ export function EmptyState({ onCreateAgent }: EmptyStateProps) { return ( Hammock - - What would you like to work on? - + What would you like to work on? - + New agent diff --git a/packages/app/src/components/headers/home-header.tsx b/packages/app/src/components/headers/home-header.tsx index c9219efb9..ebe9ce124 100644 --- a/packages/app/src/components/headers/home-header.tsx +++ b/packages/app/src/components/headers/home-header.tsx @@ -34,10 +34,7 @@ export function HomeHeader({ onCreateAgent }: HomeHeaderProps) { > - + @@ -74,6 +71,5 @@ const styles = StyleSheet.create((theme) => ({ iconButton: { padding: theme.spacing[3], borderRadius: theme.borderRadius.lg, - backgroundColor: theme.colors.muted, }, })); diff --git a/packages/app/src/hooks/use-recent-paths.ts b/packages/app/src/hooks/use-recent-paths.ts new file mode 100644 index 000000000..1b049823e --- /dev/null +++ b/packages/app/src/hooks/use-recent-paths.ts @@ -0,0 +1,68 @@ +import { useState, useEffect, useCallback } from 'react'; +import AsyncStorage from '@react-native-async-storage/async-storage'; + +const STORAGE_KEY = '@voice-dev:recent-paths'; +const MAX_RECENT_PATHS = 3; + +export interface UseRecentPathsReturn { + recentPaths: string[]; + isLoading: boolean; + addRecentPath: (path: string) => Promise; + clearRecentPaths: () => Promise; +} + +export function useRecentPaths(): UseRecentPathsReturn { + const [recentPaths, setRecentPaths] = useState([]); + const [isLoading, setIsLoading] = useState(true); + + // Load recent paths from AsyncStorage on mount + useEffect(() => { + loadRecentPaths(); + }, []); + + async function loadRecentPaths() { + try { + const stored = await AsyncStorage.getItem(STORAGE_KEY); + if (stored) { + const parsed = JSON.parse(stored) as string[]; + setRecentPaths(parsed); + } + } catch (error) { + console.error('[RecentPaths] Failed to load recent paths:', error); + // Continue with empty array + } finally { + setIsLoading(false); + } + } + + const addRecentPath = useCallback(async (path: string) => { + try { + // Remove duplicates and add to front + const filtered = recentPaths.filter((p) => p !== path); + const updated = [path, ...filtered].slice(0, MAX_RECENT_PATHS); + + setRecentPaths(updated); + await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(updated)); + } catch (error) { + console.error('[RecentPaths] Failed to save recent path:', error); + throw error; + } + }, [recentPaths]); + + const clearRecentPaths = useCallback(async () => { + try { + setRecentPaths([]); + await AsyncStorage.removeItem(STORAGE_KEY); + } catch (error) { + console.error('[RecentPaths] Failed to clear recent paths:', error); + throw error; + } + }, []); + + return { + recentPaths, + isLoading, + addRecentPath, + clearRecentPaths, + }; +} diff --git a/packages/server/src/server/acp/agent-manager.ts b/packages/server/src/server/acp/agent-manager.ts index 336010427..eabaeed04 100644 --- a/packages/server/src/server/acp/agent-manager.ts +++ b/packages/server/src/server/acp/agent-manager.ts @@ -15,6 +15,7 @@ import { type WriteTextFileResponse, } from "@agentclientprotocol/sdk"; import { v4 as uuidv4 } from "uuid"; +import { expandTilde } from "../terminal-mcp/tmux.js"; import type { AgentStatus, AgentInfo, @@ -113,7 +114,7 @@ export class AgentManager { */ async createAgent(options: CreateAgentOptions): Promise { const agentId = uuidv4(); - const cwd = options.cwd; + const cwd = expandTilde(options.cwd); // Validate that the working directory exists try { diff --git a/packages/server/src/server/terminal-mcp/tmux.ts b/packages/server/src/server/terminal-mcp/tmux.ts index d12a527c1..31a912764 100644 --- a/packages/server/src/server/terminal-mcp/tmux.ts +++ b/packages/server/src/server/terminal-mcp/tmux.ts @@ -380,7 +380,7 @@ export async function createSession(name: string): Promise { /** * Expand tilde in path to home directory */ -function expandTilde(path: string): string { +export function expandTilde(path: string): string { if (path.startsWith("~/")) { const homeDir = process.env.HOME || os.homedir(); return path.replace("~", homeDir);