From f26d7f55520707b380b75769ccba41c62557268c Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Sat, 7 Feb 2026 00:07:04 +0700 Subject: [PATCH] Update files --- packages/app/src/app/settings.tsx | 217 +++++++++++------- .../agent-form/agent-form-dropdowns.tsx | 26 ++- .../app/src/components/agent-stream-view.tsx | 5 +- packages/app/src/components/git-diff-pane.tsx | 35 +-- .../src/screens/agent/draft-agent-screen.tsx | 145 ++++++++---- 5 files changed, 261 insertions(+), 167 deletions(-) diff --git a/packages/app/src/app/settings.tsx b/packages/app/src/app/settings.tsx index d98443ae3..64d146e66 100644 --- a/packages/app/src/app/settings.tsx +++ b/packages/app/src/app/settings.tsx @@ -416,7 +416,6 @@ export default function SettingsScreen() { const [pendingRemoveHost, setPendingRemoveHost] = useState(null); const [isRemovingHost, setIsRemovingHost] = useState(false); const [editingDaemon, setEditingDaemon] = useState(null); - const [editLabel, setEditLabel] = useState(""); const [isSavingEdit, setIsSavingEdit] = useState(false); const isLoading = settingsLoading || daemonLoading; const isMountedRef = useRef(true); @@ -460,13 +459,11 @@ export default function SettingsScreen() { const handleEditDaemon = useCallback((profile: HostProfile) => { setEditingDaemon(profile); - setEditLabel(profile.label ?? ""); }, []); const handleCloseEditDaemon = useCallback(() => { if (isSavingEdit) return; setEditingDaemon(null); - setEditLabel(""); }, [isSavingEdit]); const closeAddConnectionFlow = useCallback(() => { @@ -510,17 +507,11 @@ export default function SettingsScreen() { pendingEditReopenServerId, ]); - useEffect(() => { - if (!editingDaemon) return; - if (editingDaemonLive) return; - handleCloseEditDaemon(); - }, [editingDaemon, editingDaemonLive, handleCloseEditDaemon]); - - const handleSaveEditDaemon = useCallback(async () => { + const handleSaveEditDaemon = useCallback(async (nextLabelRaw: string) => { if (!editingDaemon) return; if (isSavingEdit) return; - const nextLabel = editLabel.trim(); + const nextLabel = nextLabelRaw.trim(); if (!nextLabel) { Alert.alert("Label required", "Enter a label for this host."); return; @@ -536,7 +527,7 @@ export default function SettingsScreen() { } finally { setIsSavingEdit(false); } - }, [editLabel, editingDaemon, handleCloseEditDaemon, isSavingEdit, updateHost]); + }, [editingDaemon, handleCloseEditDaemon, isSavingEdit, updateHost]); const handleRemoveConnection = useCallback( async (serverId: string, connectionId: string) => { @@ -757,84 +748,14 @@ export default function SettingsScreen() { ) : null} - - - Label - - - - {editingDaemonLive ? ( - - Connections - - {editingDaemonLive.connections.map((conn) => { - const title = - conn.type === "relay" - ? `Relay (${conn.relayEndpoint})` - : `Direct (${conn.endpoint})`; - return ( - - - {title} - - void handleRemoveConnection(editingDaemonLive.serverId, conn.id)} - > - - Remove - - - - ); - })} - - - ) : null} - - - - - - + onSave={(label) => void handleSaveEditDaemon(label)} + onRemoveConnection={handleRemoveConnection} + /> {/* Appearance */} @@ -895,6 +816,126 @@ export default function SettingsScreen() { ); } +interface EditHostModalProps { + visible: boolean; + host: HostProfile | null; + isSaving: boolean; + onClose: () => void; + onSave: (label: string) => void; + onRemoveConnection: (serverId: string, connectionId: string) => Promise; +} + +function EditHostModal({ + visible, + host, + isSaving, + onClose, + onSave, + onRemoveConnection, +}: EditHostModalProps) { + const [draftLabel, setDraftLabel] = useState(""); + const activeServerIdRef = useRef(null); + + useEffect(() => { + if (!visible || !host) return; + if (activeServerIdRef.current !== host.serverId) { + setDraftLabel(host.label ?? ""); + activeServerIdRef.current = host.serverId; + return; + } + if (!draftLabel.trim()) { + setDraftLabel(host.label ?? ""); + } + }, [visible, host, draftLabel]); + + useEffect(() => { + if (!visible) { + activeServerIdRef.current = null; + } + }, [visible]); + + return ( + + + Label + + + + {host ? ( + + Connections + + {host.connections.map((conn) => { + const title = + conn.type === "relay" + ? `Relay (${conn.relayEndpoint})` + : `Direct (${conn.endpoint})`; + return ( + + + {title} + + void onRemoveConnection(host.serverId, conn.id)} + > + + Remove + + + + ); + })} + + + ) : null} + + + + + + + ); +} + interface DaemonCardProps { daemon: HostProfile; connectionStatus: ConnectionStatus; diff --git a/packages/app/src/components/agent-form/agent-form-dropdowns.tsx b/packages/app/src/components/agent-form/agent-form-dropdowns.tsx index 738b1310c..ae8034884 100644 --- a/packages/app/src/components/agent-form/agent-form-dropdowns.tsx +++ b/packages/app/src/components/agent-form/agent-form-dropdowns.tsx @@ -163,6 +163,15 @@ export function SelectField({ [getWebKey, onPress, preventWebDefault] ); + const normalizedValue = (value ?? "").trim(); + const normalizedPlaceholder = (placeholder ?? "").trim(); + const hasConcreteValue = + normalizedValue.length > 0 && + (normalizedPlaceholder.length === 0 || normalizedValue !== normalizedPlaceholder); + const displayText = hasConcreteValue + ? normalizedValue + : (normalizedPlaceholder || "Select..."); + return ( 0 && + (normalizedPlaceholder.length === 0 || normalizedValue !== normalizedPlaceholder); + const displayText = hasConcreteValue + ? normalizedValue + : (normalizedPlaceholder || "Select..."); return ( ) : ( - {value || placeholder || "Select..."} + {displayText} )} @@ -1426,7 +1443,10 @@ const styles = StyleSheet.create((theme) => ({ paddingVertical: theme.spacing[1], }, agentConfigRow: { - flexDirection: "row", + flexDirection: { + xs: "column", + md: "row", + }, gap: theme.spacing[2], }, agentConfigColumn: { diff --git a/packages/app/src/components/agent-stream-view.tsx b/packages/app/src/components/agent-stream-view.tsx index e5399b03f..30ae13115 100644 --- a/packages/app/src/components/agent-stream-view.tsx +++ b/packages/app/src/components/agent-stream-view.tsx @@ -1206,7 +1206,10 @@ const stylesheet = StyleSheet.create((theme) => ({ }, list: { flex: 1, - paddingHorizontal: theme.spacing[2], + paddingHorizontal: { + xs: theme.spacing[2], + md: theme.spacing[4], + }, }, streamItemWrapper: { width: "100%", diff --git a/packages/app/src/components/git-diff-pane.tsx b/packages/app/src/components/git-diff-pane.tsx index 5ca761621..6dc7a19d5 100644 --- a/packages/app/src/components/git-diff-pane.tsx +++ b/packages/app/src/components/git-diff-pane.tsx @@ -1050,24 +1050,18 @@ export function GitDiffPane({ serverId, agentId, cwd }: GitDiffPaneProps) { accessibilityRole="button" accessibilityLabel={gitActions.primary.label} > - - + {gitActions.primary.status === "pending" ? ( + + ) : ( + {gitActions.primary.icon} {getActionDisplayLabel(gitActions.primary)} - {gitActions.primary.status === "pending" ? ( - - ) : null} - + )} {gitActions.secondary.length > 0 ? ( @@ -1306,11 +1300,6 @@ const styles = StyleSheet.create((theme) => ({ justifyContent: "center", position: "relative", }, - splitButtonPrimaryInner: { - position: "relative", - alignItems: "center", - justifyContent: "center", - }, splitButtonPrimaryDisabled: { opacity: 0.6, }, @@ -1326,11 +1315,7 @@ const styles = StyleSheet.create((theme) => ({ justifyContent: "center", gap: theme.spacing[2], }, - splitButtonContentHidden: { - opacity: 0, - }, - splitButtonSpinnerOverlay: { - position: "absolute", + splitButtonSpinnerOnly: { transform: [{ scale: 0.8 }], }, splitButtonCaret: { diff --git a/packages/app/src/screens/agent/draft-agent-screen.tsx b/packages/app/src/screens/agent/draft-agent-screen.tsx index 9b4539f31..b1e1d2ff1 100644 --- a/packages/app/src/screens/agent/draft-agent-screen.tsx +++ b/packages/app/src/screens/agent/draft-agent-screen.tsx @@ -30,6 +30,7 @@ import { import { useDaemonConnections } from "@/contexts/daemon-connections-context"; import { useDaemonRegistry } from "@/contexts/daemon-registry-context"; import { formatConnectionStatus } from "@/utils/daemons"; +import { shortenPath } from "@/utils/shorten-path"; import { usePanelStore } from "@/stores/panel-store"; import { useSessionStore } from "@/stores/session-store"; import { useCreateFlowStore } from "@/stores/create-flow-store"; @@ -199,11 +200,25 @@ export function DraftAgentScreen({ isCreateFlow: true, onlineServerIds, }); + const daemonLabelByServerId = useMemo(() => { + const map = new Map(); + for (const daemon of daemons) { + const label = daemon.label?.trim(); + map.set(daemon.serverId, label && label.length > 0 ? label : daemon.serverId); + } + return map; + }, [daemons]); const hostEntry = selectedServerId ? connectionStates.get(selectedServerId) : undefined; + const selectedDaemonLabel = selectedServerId + ? daemonLabelByServerId.get(selectedServerId) + : null; const hostLabel = - hostEntry?.daemon.label ?? selectedServerId ?? "Select host"; + selectedDaemonLabel ?? + hostEntry?.daemon.label ?? + selectedServerId ?? + "Select host"; const isMobile = UnistylesRuntime.breakpoint === "xs" || UnistylesRuntime.breakpoint === "sm"; const isSidebarOpen = isMobile ? mobileView === "agent-list" : desktopAgentListOpen; @@ -561,9 +576,8 @@ export function DraftAgentScreen({ const handleSelectWorktreePath = useCallback( (path: string) => { setSelectedWorktreePath(path); - setWorkingDirFromUser(path); }, - [setWorkingDirFromUser] + [] ); useEffect(() => { @@ -591,35 +605,36 @@ export function DraftAgentScreen({ const selectedWorktreeLabel = worktreeOptions.find((option) => option.path === selectedWorktreePath)?.label ?? ""; + const displayWorkingDir = shortenPath(workingDir); const worktreeTriggerValue = worktreeMode === "create" ? "Create new worktree" : selectedWorktreeLabel || "Select worktree"; const hostOptions = useMemo( () => - Array.from(connectionStates.values()).map(({ daemon, status }) => ({ + daemons.map((daemon) => ({ id: daemon.serverId, - label: daemon.label?.trim() ? daemon.label : daemon.serverId, - description: formatConnectionStatus(status), + label: daemonLabelByServerId.get(daemon.serverId) ?? daemon.serverId, + description: formatConnectionStatus( + connectionStates.get(daemon.serverId)?.status ?? "idle" + ), })), - [connectionStates] + [connectionStates, daemonLabelByServerId, daemons] ); const worktreeComboOptions = useMemo( () => [ - { - id: "__create_new__", - label: "Create new worktree", - description: "Create a new isolated worktree", - }, { id: "__none__", label: "None", - description: "Do not use a worktree", + }, + { + id: "__create_new__", + label: "Create new worktree", }, ...worktreeOptions.map((option) => ({ id: option.path, label: option.label, - description: option.path, + description: shortenPath(option.path), })), ], [worktreeOptions] @@ -922,12 +937,12 @@ export function DraftAgentScreen({ ) : ( - + setIsWorkingDirOpen(true)} icon={} @@ -936,7 +951,7 @@ export function DraftAgentScreen({ /> )} - {trimmedWorkingDir.length > 0 && !isNonGitDirectory ? ( - - setIsWorktreePickerOpen(true)} - icon={} - showLabel={false} - valueEllipsizeMode="middle" - /> - {worktreeMode === "create" ? ( - setIsBranchOpen(true)} - disabled={repoInfoStatus === "loading"} - icon={} - showLabel={false} - /> - ) : null} - - ) : null} + {isMobile ? : null} + {isMobile && trimmedWorkingDir.length > 0 && !isNonGitDirectory ? ( + + ) : null} + {trimmedWorkingDir.length > 0 && !isNonGitDirectory ? ( + + setIsWorktreePickerOpen(true)} + icon={} + showLabel={false} + valueEllipsizeMode="middle" + /> + {worktreeMode === "create" ? ( + setIsBranchOpen(true)} + disabled={repoInfoStatus === "loading"} + icon={} + showLabel={false} + /> + ) : null} + + ) : null} {baseBranchError ? {baseBranchError} : null} {repoInfoError ? {repoInfoError} : null} {gitBlockingError ? {gitBlockingError} : null} @@ -1046,7 +1069,10 @@ export function DraftAgentScreen({ /> ({ id: path, label: path }))} + options={agentWorkingDirSuggestions.map((path) => ({ + id: path, + label: shortenPath(path), + }))} value={workingDir} onSelect={setWorkingDirFromUser} searchPlaceholder="/path/to/project" @@ -1136,7 +1162,10 @@ const styles = StyleSheet.create((theme) => ({ justifyContent: "flex-end", }, configSection: { - paddingHorizontal: theme.spacing[0], + paddingHorizontal: { + xs: theme.spacing[4], + md: theme.spacing[0], + }, paddingTop: theme.spacing[3], paddingBottom: theme.spacing[4], gap: theme.spacing[2], @@ -1149,6 +1178,22 @@ const styles = StyleSheet.create((theme) => ({ alignItems: "center", gap: theme.spacing[2], }, + stackedSelectorGroup: { + gap: theme.spacing[2], + }, + fullSelector: { + width: "100%", + }, + formSeparator: { + height: theme.borderWidth[1], + backgroundColor: { + xs: theme.colors.surface1, + sm: theme.colors.surface1, + md: theme.colors.border, + }, + marginHorizontal: theme.spacing[1], + marginVertical: theme.spacing[2], + }, topSelectorPrimary: { flex: 7, },