Redesign new agent page UI layout
- Move host selector to header (right side) with icon + name + status dot - Combine provider/model/mode into single "Agent" dropdown - Replace Git branch/worktree toggles with segmented IsolationControl (None/Branch/Worktree) - Only show Git section for git repositories (hide for non-git directories) - Wrap config section in ScrollView for proper scrolling - Simplify layout: Working Directory → Agent → Git (when applicable) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.5
parent
4cf0ea73a1
commit
26da925de6
@@ -3,20 +3,18 @@ import {
|
||||
View,
|
||||
Text,
|
||||
Pressable,
|
||||
ScrollView,
|
||||
useWindowDimensions,
|
||||
} from "react-native";
|
||||
import { useLocalSearchParams, useRouter } from "expo-router";
|
||||
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
||||
import { ChevronRight, Plus } from "lucide-react-native";
|
||||
import { ChevronRight, Plus, Monitor } from "lucide-react-native";
|
||||
import { BackHeader } from "@/components/headers/back-header";
|
||||
import { AgentList } from "@/components/agent-list";
|
||||
import { AgentInputArea } from "@/components/agent-input-area";
|
||||
import {
|
||||
AssistantDropdown,
|
||||
DropdownSheet,
|
||||
GitOptionsSection,
|
||||
ModelDropdown,
|
||||
PermissionsDropdown,
|
||||
WorkingDirectoryDropdown,
|
||||
} from "@/components/agent-form/agent-form-dropdowns";
|
||||
import { useDaemonRequest } from "@/hooks/use-daemon-request";
|
||||
@@ -120,20 +118,23 @@ export default function DraftAgentScreen() {
|
||||
: undefined;
|
||||
|
||||
const [openDropdown, setOpenDropdown] = useState<
|
||||
"host" | "provider" | "mode" | "model" | "workingDir" | "baseBranch" | null
|
||||
"host" | "provider" | "mode" | "model" | "workingDir" | "baseBranch" | "agent" | null
|
||||
>(null);
|
||||
const [errorMessage, setErrorMessage] = useState("");
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [baseBranch, setBaseBranch] = useState("");
|
||||
const [createNewBranch, setCreateNewBranch] = useState(false);
|
||||
// Isolation mode: "none" | "branch" | "worktree"
|
||||
const [isolationMode, setIsolationMode] = useState<"none" | "branch" | "worktree">("none");
|
||||
const [branchName, setBranchName] = useState("");
|
||||
const [createWorktree, setCreateWorktree] = useState(false);
|
||||
const [worktreeSlug, setWorktreeSlug] = useState("");
|
||||
const [branchNameEdited, setBranchNameEdited] = useState(false);
|
||||
const [worktreeSlugEdited, setWorktreeSlugEdited] = useState(false);
|
||||
const shouldSyncBaseBranchRef = useRef(true);
|
||||
// Derive old flags from isolationMode for backwards compatibility
|
||||
const createNewBranch = isolationMode === "branch" || isolationMode === "worktree";
|
||||
const createWorktree = isolationMode === "worktree";
|
||||
const openDropdownSheet = useCallback(
|
||||
(key: "host" | "provider" | "mode" | "model" | "workingDir" | "baseBranch") => {
|
||||
(key: "host" | "provider" | "mode" | "model" | "workingDir" | "baseBranch" | "agent") => {
|
||||
setOpenDropdown(key);
|
||||
},
|
||||
[]
|
||||
@@ -393,14 +394,12 @@ export default function DraftAgentScreen() {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
createNewBranch ||
|
||||
createWorktree ||
|
||||
isolationMode !== "none" ||
|
||||
baseBranch.trim().length > 0 ||
|
||||
branchName.trim().length > 0 ||
|
||||
worktreeSlug.trim().length > 0
|
||||
) {
|
||||
setCreateNewBranch(false);
|
||||
setCreateWorktree(false);
|
||||
setIsolationMode("none");
|
||||
setBaseBranch("");
|
||||
setBranchName("");
|
||||
setWorktreeSlug("");
|
||||
@@ -411,8 +410,7 @@ export default function DraftAgentScreen() {
|
||||
}, [
|
||||
baseBranch,
|
||||
branchName,
|
||||
createNewBranch,
|
||||
createWorktree,
|
||||
isolationMode,
|
||||
isNonGitDirectory,
|
||||
worktreeSlug,
|
||||
]);
|
||||
@@ -657,62 +655,28 @@ export default function DraftAgentScreen() {
|
||||
)}
|
||||
|
||||
<View style={styles.agentPanel}>
|
||||
<BackHeader title="New Agent" onBack={handleBackToHome} />
|
||||
<BackHeader
|
||||
title="New Agent"
|
||||
onBack={handleBackToHome}
|
||||
rightContent={
|
||||
<Pressable
|
||||
style={styles.hostBadge}
|
||||
onPress={() => openDropdownSheet("host")}
|
||||
>
|
||||
<Monitor size={14} color={theme.colors.mutedForeground} />
|
||||
<Text style={styles.hostBadgeLabel}>{hostLabel}</Text>
|
||||
<View
|
||||
style={[
|
||||
styles.hostStatusDot,
|
||||
hostEntry?.status === "online" && styles.hostStatusDotOnline,
|
||||
]}
|
||||
/>
|
||||
</Pressable>
|
||||
}
|
||||
/>
|
||||
|
||||
<View style={styles.contentContainer}>
|
||||
<ScrollView style={styles.contentContainer} contentContainerStyle={styles.configScrollContent}>
|
||||
<View style={styles.configSection}>
|
||||
{renderConfigRow({
|
||||
label: "Host",
|
||||
value: hostLabel,
|
||||
meta: hostStatus,
|
||||
onPress: () => openDropdownSheet("host"),
|
||||
})}
|
||||
<AssistantDropdown
|
||||
providerDefinitions={providerDefinitions}
|
||||
disabled={false}
|
||||
selectedProvider={selectedProvider}
|
||||
isOpen={openDropdown === "provider"}
|
||||
onOpen={() => openDropdownSheet("provider")}
|
||||
onClose={closeDropdown}
|
||||
onSelect={setProviderFromUser}
|
||||
label="Provider"
|
||||
placeholder="Select provider"
|
||||
sheetTitle="Choose Provider"
|
||||
wrapInContainer={false}
|
||||
renderTrigger={renderDropdownTrigger}
|
||||
/>
|
||||
<PermissionsDropdown
|
||||
disabled={false}
|
||||
modeOptions={modeOptions}
|
||||
selectedMode={selectedMode}
|
||||
isOpen={openDropdown === "mode"}
|
||||
onOpen={() => openDropdownSheet("mode")}
|
||||
onClose={closeDropdown}
|
||||
onSelect={setModeFromUser}
|
||||
label="Mode"
|
||||
placeholder="Select mode"
|
||||
sheetTitle="Mode"
|
||||
wrapInContainer={false}
|
||||
renderTrigger={renderDropdownTrigger}
|
||||
/>
|
||||
<ModelDropdown
|
||||
models={availableModels}
|
||||
selectedModel={selectedModel}
|
||||
isLoading={isModelLoading}
|
||||
error={modelError}
|
||||
isOpen={openDropdown === "model"}
|
||||
onOpen={() => {
|
||||
refreshProviderModels();
|
||||
openDropdownSheet("model");
|
||||
}}
|
||||
onClose={closeDropdown}
|
||||
onSelect={setModelFromUser}
|
||||
onClear={() => setModelFromUser("")}
|
||||
onRefresh={refreshProviderModels}
|
||||
label="Model"
|
||||
wrapInContainer={false}
|
||||
renderTrigger={renderDropdownTrigger}
|
||||
/>
|
||||
<WorkingDirectoryDropdown
|
||||
workingDir={workingDir}
|
||||
errorMessage=""
|
||||
@@ -726,7 +690,13 @@ export default function DraftAgentScreen() {
|
||||
wrapInContainer={false}
|
||||
renderTrigger={renderDropdownTrigger}
|
||||
/>
|
||||
{trimmedWorkingDir.length > 0 ? (
|
||||
{renderConfigRow({
|
||||
label: "Agent",
|
||||
value: `${providerDefinitions.find((p) => p.id === selectedProvider)?.label ?? selectedProvider} · ${selectedModel || "auto"}`,
|
||||
onPress: () => openDropdownSheet("agent"),
|
||||
})}
|
||||
{/* Git section - only show for git repos */}
|
||||
{trimmedWorkingDir.length > 0 && !isNonGitDirectory ? (
|
||||
<GitOptionsSection
|
||||
baseBranch={baseBranch}
|
||||
onBaseBranchChange={handleBaseBranchChange}
|
||||
@@ -736,43 +706,37 @@ export default function DraftAgentScreen() {
|
||||
helperText={gitHelperText}
|
||||
isGitDisabled={Boolean(gitHelperText)}
|
||||
warning={
|
||||
!createWorktree && repoInfo?.isDirty
|
||||
repoInfo?.isDirty
|
||||
? "Working directory has uncommitted changes"
|
||||
: null
|
||||
}
|
||||
createNewBranch={createNewBranch}
|
||||
onToggleCreateNewBranch={(next) => {
|
||||
setCreateNewBranch(next);
|
||||
if (next) {
|
||||
isolationMode={isolationMode}
|
||||
onIsolationModeChange={(mode) => {
|
||||
setIsolationMode(mode);
|
||||
if (mode === "none") {
|
||||
setBranchName("");
|
||||
setWorktreeSlug("");
|
||||
setBranchNameEdited(false);
|
||||
setWorktreeSlugEdited(false);
|
||||
} else if (mode === "branch") {
|
||||
if (!branchNameEdited) {
|
||||
const slug = slugifyWorktreeName(baseBranch || "");
|
||||
setBranchName(slug);
|
||||
}
|
||||
} else {
|
||||
setBranchName("");
|
||||
setBranchNameEdited(false);
|
||||
}
|
||||
}}
|
||||
branchName={branchName}
|
||||
onBranchNameChange={(value) => {
|
||||
setBranchName(slugifyWorktreeName(value));
|
||||
setBranchNameEdited(true);
|
||||
}}
|
||||
createWorktree={createWorktree}
|
||||
onToggleCreateWorktree={(next) => {
|
||||
setCreateWorktree(next);
|
||||
if (next) {
|
||||
} else if (mode === "worktree") {
|
||||
if (!worktreeSlugEdited) {
|
||||
const slug = slugifyWorktreeName(
|
||||
branchName || baseBranch || ""
|
||||
);
|
||||
setWorktreeSlug(slug);
|
||||
}
|
||||
} else {
|
||||
setWorktreeSlug("");
|
||||
setWorktreeSlugEdited(false);
|
||||
}
|
||||
}}
|
||||
branchName={branchName}
|
||||
onBranchNameChange={(value) => {
|
||||
setBranchName(slugifyWorktreeName(value));
|
||||
setBranchNameEdited(true);
|
||||
}}
|
||||
worktreeSlug={worktreeSlug}
|
||||
onWorktreeSlugChange={(value) => {
|
||||
setWorktreeSlug(slugifyWorktreeName(value));
|
||||
@@ -824,13 +788,118 @@ export default function DraftAgentScreen() {
|
||||
)}
|
||||
</DropdownSheet>
|
||||
|
||||
{/* Agent dropdown sheet - provider + model + mode */}
|
||||
<DropdownSheet
|
||||
title="Agent"
|
||||
visible={openDropdown === "agent"}
|
||||
onClose={closeDropdown}
|
||||
>
|
||||
<View style={styles.agentSheetSection}>
|
||||
<Text style={styles.agentSheetSectionLabel}>Provider</Text>
|
||||
<View style={styles.dropdownSheetList}>
|
||||
{providerDefinitions.map((definition) => {
|
||||
const isSelected = definition.id === selectedProvider;
|
||||
return (
|
||||
<Pressable
|
||||
key={definition.id}
|
||||
style={[
|
||||
styles.dropdownSheetOption,
|
||||
isSelected && styles.dropdownSheetOptionSelected,
|
||||
]}
|
||||
onPress={() => {
|
||||
setProviderFromUser(definition.id);
|
||||
}}
|
||||
>
|
||||
<Text style={styles.dropdownSheetOptionLabel}>
|
||||
{definition.label}
|
||||
</Text>
|
||||
{definition.description ? (
|
||||
<Text style={styles.dropdownSheetOptionDescription}>
|
||||
{definition.description}
|
||||
</Text>
|
||||
) : null}
|
||||
</Pressable>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View style={styles.agentSheetSection}>
|
||||
<Text style={styles.agentSheetSectionLabel}>Model</Text>
|
||||
<View style={styles.dropdownSheetList}>
|
||||
<Pressable
|
||||
style={[
|
||||
styles.dropdownSheetOption,
|
||||
!selectedModel && styles.dropdownSheetOptionSelected,
|
||||
]}
|
||||
onPress={() => setModelFromUser("")}
|
||||
>
|
||||
<Text style={styles.dropdownSheetOptionLabel}>
|
||||
Automatic (provider default)
|
||||
</Text>
|
||||
</Pressable>
|
||||
{availableModels.map((model) => {
|
||||
const isSelected = model.id === selectedModel;
|
||||
return (
|
||||
<Pressable
|
||||
key={model.id}
|
||||
style={[
|
||||
styles.dropdownSheetOption,
|
||||
isSelected && styles.dropdownSheetOptionSelected,
|
||||
]}
|
||||
onPress={() => setModelFromUser(model.id)}
|
||||
>
|
||||
<Text style={styles.dropdownSheetOptionLabel}>
|
||||
{model.label}
|
||||
</Text>
|
||||
{model.description ? (
|
||||
<Text style={styles.dropdownSheetOptionDescription}>
|
||||
{model.description}
|
||||
</Text>
|
||||
) : null}
|
||||
</Pressable>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{modeOptions.length > 0 ? (
|
||||
<View style={styles.agentSheetSection}>
|
||||
<Text style={styles.agentSheetSectionLabel}>Mode</Text>
|
||||
<View style={styles.dropdownSheetList}>
|
||||
{modeOptions.map((mode) => {
|
||||
const isSelected = mode.id === selectedMode;
|
||||
return (
|
||||
<Pressable
|
||||
key={mode.id}
|
||||
style={[
|
||||
styles.dropdownSheetOption,
|
||||
isSelected && styles.dropdownSheetOptionSelected,
|
||||
]}
|
||||
onPress={() => setModeFromUser(mode.id)}
|
||||
>
|
||||
<Text style={styles.dropdownSheetOptionLabel}>
|
||||
{mode.label}
|
||||
</Text>
|
||||
{mode.description ? (
|
||||
<Text style={styles.dropdownSheetOptionDescription}>
|
||||
{mode.description}
|
||||
</Text>
|
||||
) : null}
|
||||
</Pressable>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
</View>
|
||||
) : null}
|
||||
</DropdownSheet>
|
||||
|
||||
{errorMessage ? (
|
||||
<View style={styles.errorContainer}>
|
||||
<Text style={styles.errorText}>{errorMessage}</Text>
|
||||
</View>
|
||||
) : null}
|
||||
<View style={styles.streamContainer} />
|
||||
</View>
|
||||
</ScrollView>
|
||||
<AgentInputArea
|
||||
agentId={DRAFT_AGENT_ID}
|
||||
serverId={selectedServerId ?? ""}
|
||||
@@ -881,10 +950,13 @@ const styles = StyleSheet.create((theme) => ({
|
||||
contentContainer: {
|
||||
flex: 1,
|
||||
},
|
||||
configScrollContent: {
|
||||
flexGrow: 1,
|
||||
},
|
||||
configSection: {
|
||||
paddingHorizontal: theme.spacing[4],
|
||||
paddingTop: theme.spacing[3],
|
||||
paddingBottom: theme.spacing[2],
|
||||
paddingBottom: theme.spacing[4],
|
||||
gap: theme.spacing[2],
|
||||
},
|
||||
configRow: {
|
||||
@@ -949,9 +1021,6 @@ const styles = StyleSheet.create((theme) => ({
|
||||
fontSize: theme.fontSize.sm,
|
||||
marginTop: theme.spacing[1],
|
||||
},
|
||||
streamContainer: {
|
||||
flex: 1,
|
||||
},
|
||||
errorContainer: {
|
||||
paddingHorizontal: theme.spacing[4],
|
||||
paddingVertical: theme.spacing[3],
|
||||
@@ -964,4 +1033,36 @@ const styles = StyleSheet.create((theme) => ({
|
||||
color: theme.colors.destructiveForeground,
|
||||
fontSize: theme.fontSize.sm,
|
||||
},
|
||||
hostBadge: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: theme.spacing[2],
|
||||
paddingHorizontal: theme.spacing[3],
|
||||
paddingVertical: theme.spacing[2],
|
||||
backgroundColor: theme.colors.muted,
|
||||
borderRadius: theme.borderRadius.full,
|
||||
},
|
||||
hostBadgeLabel: {
|
||||
color: theme.colors.mutedForeground,
|
||||
fontSize: theme.fontSize.xs,
|
||||
fontWeight: theme.fontWeight.semibold,
|
||||
},
|
||||
hostStatusDot: {
|
||||
width: 6,
|
||||
height: 6,
|
||||
borderRadius: theme.borderRadius.full,
|
||||
backgroundColor: theme.colors.mutedForeground,
|
||||
},
|
||||
hostStatusDotOnline: {
|
||||
backgroundColor: theme.colors.palette.green[500],
|
||||
},
|
||||
agentSheetSection: {
|
||||
marginBottom: theme.spacing[4],
|
||||
},
|
||||
agentSheetSectionLabel: {
|
||||
fontSize: theme.fontSize.sm,
|
||||
fontWeight: theme.fontWeight.semibold,
|
||||
color: theme.colors.foreground,
|
||||
marginBottom: theme.spacing[2],
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -609,6 +609,57 @@ export function ToggleRow({
|
||||
);
|
||||
}
|
||||
|
||||
export type IsolationMode = "none" | "branch" | "worktree";
|
||||
|
||||
interface IsolationControlProps {
|
||||
value: IsolationMode;
|
||||
onChange: (mode: IsolationMode) => void;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export function IsolationControl({
|
||||
value,
|
||||
onChange,
|
||||
disabled,
|
||||
}: IsolationControlProps): ReactElement {
|
||||
const options: Array<{ id: IsolationMode; label: string }> = [
|
||||
{ id: "none", label: "None" },
|
||||
{ id: "branch", label: "Branch" },
|
||||
{ id: "worktree", label: "Worktree" },
|
||||
];
|
||||
|
||||
return (
|
||||
<View style={[styles.segmentedControl, disabled && styles.segmentedControlDisabled]}>
|
||||
{options.map((option) => {
|
||||
const isSelected = option.id === value;
|
||||
return (
|
||||
<Pressable
|
||||
key={option.id}
|
||||
onPress={() => {
|
||||
if (!disabled) {
|
||||
onChange(option.id);
|
||||
}
|
||||
}}
|
||||
style={[
|
||||
styles.segmentedOption,
|
||||
isSelected && styles.segmentedOptionSelected,
|
||||
]}
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
styles.segmentedOptionText,
|
||||
isSelected && styles.segmentedOptionTextSelected,
|
||||
]}
|
||||
>
|
||||
{option.label}
|
||||
</Text>
|
||||
</Pressable>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
export interface GitOptionsSectionProps {
|
||||
baseBranch: string;
|
||||
onBaseBranchChange: (value: string) => void;
|
||||
@@ -617,12 +668,10 @@ export interface GitOptionsSectionProps {
|
||||
repoError: string | null;
|
||||
helperText?: string | null;
|
||||
warning: string | null;
|
||||
createNewBranch: boolean;
|
||||
onToggleCreateNewBranch: (value: boolean) => void;
|
||||
isolationMode: IsolationMode;
|
||||
onIsolationModeChange: (mode: IsolationMode) => void;
|
||||
branchName: string;
|
||||
onBranchNameChange: (value: string) => void;
|
||||
createWorktree: boolean;
|
||||
onToggleCreateWorktree: (value: boolean) => void;
|
||||
worktreeSlug: string;
|
||||
onWorktreeSlugChange: (value: string) => void;
|
||||
gitValidationError: string | null;
|
||||
@@ -640,12 +689,10 @@ export function GitOptionsSection({
|
||||
repoError,
|
||||
helperText,
|
||||
warning,
|
||||
createNewBranch,
|
||||
onToggleCreateNewBranch,
|
||||
isolationMode,
|
||||
onIsolationModeChange,
|
||||
branchName,
|
||||
onBranchNameChange,
|
||||
createWorktree,
|
||||
onToggleCreateWorktree,
|
||||
worktreeSlug,
|
||||
onWorktreeSlugChange,
|
||||
gitValidationError,
|
||||
@@ -668,6 +715,10 @@ export function GitOptionsSection({
|
||||
const baseInputRef = useRef<TextInput | null>(null);
|
||||
const gitInputsDisabled = Boolean(isGitDisabled) || status === "loading";
|
||||
|
||||
// Show warning only for "branch" mode (requires clean state for checkout)
|
||||
// Worktree mode doesn't require clean state
|
||||
const showDirtyWarning = isolationMode === "branch" && warning;
|
||||
|
||||
useEffect(() => {
|
||||
if (isBaseDropdownOpen) {
|
||||
setBranchSearch("");
|
||||
@@ -677,25 +728,19 @@ export function GitOptionsSection({
|
||||
|
||||
return (
|
||||
<View style={styles.formSection}>
|
||||
<Text style={styles.label}>Git Setup</Text>
|
||||
<Text style={styles.helperText}>
|
||||
Choose a base branch, then optionally create a feature branch or
|
||||
isolated worktree.
|
||||
</Text>
|
||||
|
||||
<DropdownField
|
||||
label="Base Branch"
|
||||
label="Git"
|
||||
value={baseBranch}
|
||||
placeholder={currentBranchLabel || "main"}
|
||||
onPress={onToggleBaseDropdown}
|
||||
disabled={gitInputsDisabled}
|
||||
errorMessage={repoError}
|
||||
warningMessage={!gitValidationError && !isGitDisabled ? warning : null}
|
||||
warningMessage={!gitValidationError && !isGitDisabled ? (showDirtyWarning ? warning : null) : null}
|
||||
helperText={
|
||||
helperText ??
|
||||
(status === "loading"
|
||||
? "Inspecting repository…"
|
||||
: "Search existing branches, then tap to select.")
|
||||
: undefined)
|
||||
}
|
||||
/>
|
||||
<DropdownSheet
|
||||
@@ -756,39 +801,22 @@ export function GitOptionsSection({
|
||||
) : null}
|
||||
</DropdownSheet>
|
||||
|
||||
<ToggleRow
|
||||
label="New Branch"
|
||||
description="Create a feature branch before launching the agent"
|
||||
value={createNewBranch}
|
||||
onToggle={onToggleCreateNewBranch}
|
||||
disabled={isGitDisabled}
|
||||
/>
|
||||
{createNewBranch ? (
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
placeholder="feature-branch-name"
|
||||
placeholderTextColor={defaultTheme.colors.mutedForeground}
|
||||
value={branchName}
|
||||
onChangeText={onBranchNameChange}
|
||||
autoCapitalize="none"
|
||||
autoCorrect={false}
|
||||
<View style={styles.formSection}>
|
||||
<Text style={styles.label}>Isolation</Text>
|
||||
<IsolationControl
|
||||
value={isolationMode}
|
||||
onChange={onIsolationModeChange}
|
||||
disabled={isGitDisabled}
|
||||
/>
|
||||
) : null}
|
||||
</View>
|
||||
|
||||
<ToggleRow
|
||||
label="Create Worktree"
|
||||
description="Use an isolated directory so your current branch stays untouched"
|
||||
value={createWorktree}
|
||||
onToggle={onToggleCreateWorktree}
|
||||
disabled={isGitDisabled}
|
||||
/>
|
||||
{createWorktree ? (
|
||||
{isolationMode !== "none" ? (
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
placeholder={branchName || "feature-worktree"}
|
||||
placeholder={isolationMode === "worktree" ? "worktree-name" : "feature-branch-name"}
|
||||
placeholderTextColor={defaultTheme.colors.mutedForeground}
|
||||
value={worktreeSlug}
|
||||
onChangeText={onWorktreeSlugChange}
|
||||
value={isolationMode === "worktree" ? worktreeSlug : branchName}
|
||||
onChangeText={isolationMode === "worktree" ? onWorktreeSlugChange : onBranchNameChange}
|
||||
autoCapitalize="none"
|
||||
autoCorrect={false}
|
||||
/>
|
||||
@@ -988,4 +1016,32 @@ const styles = StyleSheet.create((theme) => ({
|
||||
alignItems: "center",
|
||||
gap: theme.spacing[2],
|
||||
},
|
||||
segmentedControl: {
|
||||
flexDirection: "row",
|
||||
backgroundColor: theme.colors.muted,
|
||||
borderRadius: theme.borderRadius.lg,
|
||||
padding: theme.spacing[1],
|
||||
},
|
||||
segmentedControlDisabled: {
|
||||
opacity: theme.opacity[50],
|
||||
},
|
||||
segmentedOption: {
|
||||
flex: 1,
|
||||
paddingVertical: theme.spacing[2],
|
||||
paddingHorizontal: theme.spacing[3],
|
||||
borderRadius: theme.borderRadius.md,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
},
|
||||
segmentedOptionSelected: {
|
||||
backgroundColor: theme.colors.background,
|
||||
},
|
||||
segmentedOptionText: {
|
||||
color: theme.colors.mutedForeground,
|
||||
fontSize: theme.fontSize.sm,
|
||||
fontWeight: theme.fontWeight.semibold,
|
||||
},
|
||||
segmentedOptionTextSelected: {
|
||||
color: theme.colors.foreground,
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -397,3 +397,91 @@ Build a new Codex MCP provider side‑by‑side with the existing Codex SDK prov
|
||||
**FILES CHANGED**:
|
||||
- `packages/app/src/app/agent/[serverId]/[agentId].tsx:419-437`
|
||||
|
||||
- [⏳] **REFACTOR (App)**: Redesign new agent page UI layout per mockup.
|
||||
|
||||
**TARGET LAYOUT**:
|
||||
```
|
||||
┌─────────────────────────────────────────────────────┐
|
||||
│ ← New Agent 🖥 local 🟢 │
|
||||
├─────────────────────────────────────────────────────┤
|
||||
│ WORKING DIRECTORY │
|
||||
│ ┌───────────────────────────────────────────────┐ │
|
||||
│ │ 📁 voice-dev 〉 │ │
|
||||
│ └───────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ AGENT │
|
||||
│ ┌───────────────────────────────────────────────┐ │
|
||||
│ │ ✦ Claude · opus-4.5 〉 │ │
|
||||
│ └───────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ GIT (only if working dir is a git repo) │
|
||||
│ ┌───────────────────────────────────────────────┐ │
|
||||
│ │ 🌿 main 〉 │ │
|
||||
│ └───────────────────────────────────────────────┘ │
|
||||
│ ⚠️ Uncommitted changes (only if Branch selected) │
|
||||
│ │
|
||||
│ Isolation (only if working dir is a git repo) │
|
||||
│ ┌────────────┬────────────┬────────────┐ │
|
||||
│ │ None │ Branch │ Worktree │ │
|
||||
│ └────────────┴────────────┴────────────┘ │
|
||||
│ [Branch name input - only if Branch/Worktree] │
|
||||
│ │
|
||||
├─────────────────────────────────────────────────────┤
|
||||
│ ┌───────────────────────────────────────────────┐ │
|
||||
│ │ Message agent... │ │
|
||||
│ └───────────────────────────────────────────────┘ │
|
||||
│ 📎 🎤 ⟨⦿⟩ │
|
||||
│ Bypass Permissions ▼ │
|
||||
└─────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**CHANGES REQUIRED**:
|
||||
|
||||
1. **Host in header** (not a form row):
|
||||
- Move server/host selector to header bar, right side
|
||||
- Show as icon + name + status dot (🖥 local 🟢)
|
||||
- Tappable to change host
|
||||
- Remove the separate "Host" form row
|
||||
|
||||
2. **Rename "Model" to "Agent"**:
|
||||
- Single row showing provider + model: `✦ Claude · opus-4.5`
|
||||
- Tapping opens picker with TWO dropdowns: provider first, then model
|
||||
- Provider shown as icon or word prefix
|
||||
|
||||
3. **Git section visibility**:
|
||||
- ONLY show "GIT" row and "Isolation" control if working directory is a git repo
|
||||
- If not a git repo, hide entire git section (no greyed out state, just hidden)
|
||||
|
||||
4. **Isolation as segmented control** (replaces toggle checkboxes):
|
||||
- Three options: None | Branch | Worktree
|
||||
- Default: None (no isolation, use current branch as-is)
|
||||
- **None**: No branch input shown, no git operations
|
||||
- **Branch**: Shows branch name input below. User can select existing branch OR type new name to create
|
||||
- **Worktree**: Shows branch name input below. Creates new worktree. Worktree implies branch creation.
|
||||
|
||||
5. **Branch name input**:
|
||||
- Only visible when isolation is Branch or Worktree
|
||||
- Auto-generated from prompt (slugified) like before
|
||||
- User can edit manually
|
||||
- For Branch: can also select from existing branches dropdown
|
||||
- For Worktree: always new branch name (worktree slug derived from it)
|
||||
|
||||
6. **Uncommitted changes warning**:
|
||||
- ONLY show when isolation is "Branch" (switching branches requires clean state)
|
||||
- Do NOT show for "None" (staying on current branch)
|
||||
- Do NOT show for "Worktree" (creating worktree does NOT require clean state)
|
||||
|
||||
7. **Mode under input area**:
|
||||
- Move mode selector (Bypass Permissions dropdown) BELOW the text input
|
||||
- Check how `[agentId].tsx` shows mode when agent is running - use same pattern
|
||||
- **CRITICAL**: Refactor `AgentInputArea` to be a pure display component
|
||||
- Both new.tsx and [agentId].tsx should use the SAME component to prevent drift
|
||||
|
||||
**REFACTORING REQUIREMENT**:
|
||||
The `AgentInputArea` component should be refactored to accept all UI elements as props/children so both screens use identical rendering. Do NOT duplicate mode dropdown logic. Study `[agentId].tsx` to see how it shows mode during agent runs.
|
||||
|
||||
**FILES TO MODIFY**:
|
||||
- `packages/app/src/app/agent/new.tsx` - Layout changes
|
||||
- `packages/app/src/components/agent-input-area.tsx` - Refactor for reuse
|
||||
- `packages/app/src/components/agent-form/agent-form-dropdowns.tsx` - Segmented control
|
||||
|
||||
|
||||
Reference in New Issue
Block a user