import { useRouter } from "expo-router";import { TextInputWrapper } from "expo-paste-input";import { useCallback, useEffect, useMemo } from "react";import { View } from "react-native";import { KeyboardStickyView, useKeyboardState } from "react-native-keyboard-controller";import { useSafeAreaInsets } from "react-native-safe-area-context";import { useThemeColor } from "../../lib/useThemeColor"; import { EnvironmentId, type ModelSelection } from "@t3tools/contracts"; import { AppTextInput as TextInput } from "../../components/AppText";import { ComposerAttachmentStrip } from "../../components/ComposerAttachmentStrip";import { ControlPill, ControlPillMenu } from "../../components/ControlPill";import { ProviderIcon } from "../../components/ProviderIcon"; import { convertPastedImagesToAttachments, pickComposerImages } from "../../lib/composerImages";import { buildThreadRoutePath } from "../../lib/routes";import { useRemoteCatalog } from "../../state/use-remote-catalog";import { useNativePaste } from "../../lib/useNativePaste";import { CLAUDE_AGENT_EFFORT_OPTIONS } from "./claudeEffortOptions";import { NewTaskSheetHeader } from "./NewTaskSheetHeader";import { branchBadgeLabel, useNewTaskFlow } from "./new-task-flow-provider";import { useProjectActions } from "./use-project-actions"; function withModelSelectionOption( selection: ModelSelection, id: string, value: string | boolean | undefined,): ModelSelection { const options = (selection.options ?? []).filter((option) => option.id !== id); return { ...selection, options: value === undefined ? options : [...options, { id, value }], };} export function NewTaskDraftScreen(props: { readonly initialProjectRef?: { readonly environmentId?: string; readonly projectId?: string; };}) { const { projects } = useRemoteCatalog(); const { onCreateThreadWithOptions } = useProjectActions(); const flow = useNewTaskFlow(); const router = useRouter(); const insets = useSafeAreaInsets(); const isKeyboardVisible = useKeyboardState((state) => state.isVisible); const controlsBottomPadding = isKeyboardVisible ? 8 : Math.max(insets.bottom, 10); const { logicalProjects, selectedProject, setProject } = flow; const borderColor = useThemeColor("--color-border"); useEffect(() => { if (props.initialProjectRef?.environmentId && props.initialProjectRef?.projectId) { const directProject = projects.find( (project) => project.environmentId === props.initialProjectRef?.environmentId && project.id === props.initialProjectRef?.projectId, ) ?? null; if (directProject) { setProject(directProject); return; } } if (selectedProject) { return; } if (logicalProjects.length === 1) { setProject(logicalProjects[0]!.project); return; } router.replace("/new"); }, [ logicalProjects, projects, props.initialProjectRef?.environmentId, props.initialProjectRef?.projectId, router, selectedProject, setProject, ]); useEffect(() => { if (!selectedProject) { return; } void flow.loadBranches(); }, [flow, selectedProject]); const environmentMenuActions = useMemo( () => flow.environments.map((environment) => ({ id: `environment:${environment.environmentId}`, title: environment.environmentLabel, state: flow.selectedEnvironmentId === environment.environmentId ? ("on" as const) : undefined, })), [flow.environments, flow.selectedEnvironmentId], ); const modelMenuActions = useMemo( () => flow.providerGroups.map((group) => ({ id: `provider:${group.providerKey}`, title: group.providerLabel, subtitle: group.models.find( (model) => flow.selectedModel && model.selection.instanceId === flow.selectedModel.instanceId && model.selection.model === flow.selectedModel.model, )?.label, subactions: group.models.map((option) => ({ id: `model:${option.key}`, title: option.label, state: flow.selectedModel && option.selection.instanceId === flow.selectedModel.instanceId && option.selection.model === flow.selectedModel.model ? ("on" as const) : undefined, })), })), [flow.providerGroups, flow.selectedModel], ); const optionsMenuActions = useMemo( () => [ { id: "options-effort", title: "Effort", subtitle: `${flow.effort.charAt(0).toUpperCase()}${flow.effort.slice(1)}`, subactions: CLAUDE_AGENT_EFFORT_OPTIONS.map((level) => ({ id: `options:effort:${level}`, title: `${level}${level === "high" ? " (default)" : ""}`, state: flow.effort === level ? ("on" as const) : undefined, })), }, { id: "options-fast-mode", title: "Fast Mode", subtitle: flow.fastMode ? "On" : "Off", subactions: ([false, true] as const).map((value) => ({ id: `options:fast-mode:${value ? "on" : "off"}`, title: value ? "On" : "Off", state: flow.fastMode === value ? ("on" as const) : undefined, })), }, { id: "options-context-window", title: "Context Window", subtitle: flow.contextWindow, subactions: (["200k", "1M"] as const).map((value) => ({ id: `options:context-window:${value}`, title: `${value}${value === "1M" ? " (default)" : ""}`, state: flow.contextWindow === value ? ("on" as const) : undefined, })), }, { id: "options-runtime", title: "Runtime", subtitle: flow.runtimeMode === "approval-required" ? "Approve actions" : flow.runtimeMode === "auto-accept-edits" ? "Auto-accept edits" : "Full access", subactions: [ { id: "options:runtime:approval-required", title: "Approve actions" }, { id: "options:runtime:auto-accept-edits", title: "Auto-accept edits" }, { id: "options:runtime:full-access", title: "Full access" }, ].map((option) => { const value = option.id.replace("options:runtime:", ""); return { id: option.id, title: option.title, state: flow.runtimeMode === value ? ("on" as const) : undefined, }; }), }, { id: "options-interaction", title: "Interaction", subtitle: flow.interactionMode === "plan" ? "Plan" : "Default", subactions: [ { id: "options:interaction:default", title: "Default" }, { id: "options:interaction:plan", title: "Plan" }, ].map((option) => { const value = option.id.replace("options:interaction:", ""); return { id: option.id, title: option.title, state: flow.interactionMode === value ? ("on" as const) : undefined, }; }), }, ], [flow.contextWindow, flow.effort, flow.fastMode, flow.interactionMode, flow.runtimeMode], ); const workspaceMenuActions = useMemo(() => { const branchActions = flow.availableBranches.length === 0 ? [ { id: "workspace:branch:none", title: flow.branchesLoading ? "Loading branches…" : "No branches available", attributes: { disabled: true }, }, ] : flow.availableBranches.slice(0, 12).map((branch) => { const badge = branchBadgeLabel({ branch, project: flow.selectedProject, }); return { id: `workspace:branch:${branch.name}`, title: branch.name, subtitle: badge ? badge.toUpperCase() : undefined, state: flow.selectedBranchName === branch.name ? ("on" as const) : undefined, }; }); return [ { id: "workspace:mode", title: "Mode", subtitle: flow.workspaceMode === "local" ? "Local" : "Worktree", subactions: (["local", "worktree"] as const).map((value) => ({ id: `workspace:mode:${value}`, title: value === "local" ? "Local" : "Worktree", state: flow.workspaceMode === value ? ("on" as const) : undefined, })), }, { id: "workspace:branch", title: "Branch", subtitle: flow.selectedBranchName ?? "Choose branch", subactions: branchActions, }, ]; }, [ flow.availableBranches, flow.branchesLoading, flow.selectedBranchName, flow.selectedProject, flow.workspaceMode, ]); function handleModelMenuAction(event: string) { if (!event.startsWith("model:")) { return; } // Defer state update so the native menu dismiss animation completes // before re-rendering the menu actions (prevents submenu jump). setTimeout(() => { flow.setSelectedModelKey(event.slice("model:".length)); }, 150); } function handleEnvironmentMenuAction(event: string) { if (!event.startsWith("environment:")) { return; } flow.selectEnvironment(EnvironmentId.make(event.slice("environment:".length))); } function handleOptionsMenuAction(event: string) { if (event.startsWith("options:effort:")) { flow.setEffort(event.slice("options:effort:".length) as typeof flow.effort); return; } if (event.startsWith("options:fast-mode:")) { flow.setFastMode(event.endsWith(":on")); return; } if (event.startsWith("options:context-window:")) { flow.setContextWindow(event.slice("options:context-window:".length)); return; } if (event.startsWith("options:runtime:")) { flow.setRuntimeMode( event.slice("options:runtime:".length) as Parameters<typeof flow.setRuntimeMode>[0], ); return; } if (event.startsWith("options:interaction:")) { flow.setInteractionMode( event.slice("options:interaction:".length) as Parameters<typeof flow.setInteractionMode>[0], ); } } function handleWorkspaceMenuAction(event: string) { if (event.startsWith("workspace:mode:")) { flow.setWorkspaceMode( event.slice("workspace:mode:".length) as Parameters<typeof flow.setWorkspaceMode>[0], ); return; } if (event.startsWith("workspace:branch:")) { const branchName = event.slice("workspace:branch:".length); const branch = flow.availableBranches.find((candidate) => candidate.name === branchName); if (branch) { flow.selectBranch(branch); } } } async function handlePickImages(): Promise<void> { const result = await pickComposerImages({ existingCount: flow.attachments.length }); if (result.images.length > 0) { flow.appendAttachments(result.images); } } const handleNativePasteImages = useCallback( async (uris: ReadonlyArray<string>) => { try { const images = await convertPastedImagesToAttachments({ uris, existingCount: flow.attachments.length, }); if (images.length > 0) { flow.appendAttachments(images); } } catch (error) { console.error("[native paste] error converting images", error); } }, [flow], ); const handleNativePaste = useNativePaste((uris) => { void handleNativePasteImages(uris); }); async function handleStart(): Promise<void> { if ( !flow.selectedProject || !flow.selectedModel || flow.prompt.trim().length === 0 || flow.submitting || (flow.workspaceMode === "worktree" && !flow.selectedBranchName) ) { return; } flow.setSubmitting(true); try { const modelWithOptions: ModelSelection = flow.selectedModelOption?.providerDriver === "claudeAgent" ? withModelSelectionOption( withModelSelectionOption( withModelSelectionOption(flow.selectedModel, "effort", flow.effort), "fastMode", flow.fastMode || undefined, ), "contextWindow", flow.contextWindow, ) : flow.selectedModelOption?.providerDriver === "codex" ? withModelSelectionOption(flow.selectedModel, "fastMode", flow.fastMode || undefined) : flow.selectedModel; const createdThread = await onCreateThreadWithOptions({ project: flow.selectedProject, modelSelection: modelWithOptions, envMode: flow.workspaceMode, branch: flow.selectedBranchName, worktreePath: flow.workspaceMode === "worktree" ? null : flow.selectedWorktreePath, runtimeMode: flow.runtimeMode, interactionMode: flow.interactionMode, initialMessageText: flow.prompt.trim(), initialAttachments: flow.attachments, }); if (createdThread) { router.replace(buildThreadRoutePath(createdThread)); } } finally { flow.setSubmitting(false); } } if (!selectedProject) { return ( <View className="flex-1 bg-sheet"> <NewTaskSheetHeader title="Loading task" /> </View> ); } return ( <View className="flex-1 bg-sheet"> <NewTaskSheetHeader title={selectedProject.title} control={ flow.logicalProjects.length > 1 ? { icon: "chevron.left", onPress: () => router.back() } : undefined } /> <View style={{ flex: 1, paddingHorizontal: 20, paddingTop: 8 }}> <TextInputWrapper onPaste={(payload) => void handleNativePaste(payload)} style={{ flex: 1 }} > <TextInput multiline value={flow.prompt} onChangeText={flow.setPrompt} placeholder={`Describe a coding task in ${selectedProject.title}`} textAlignVertical="top" className="h-full flex-1 border-0 bg-transparent text-[18px] leading-[28px]" style={{ flex: 1 }} /> </TextInputWrapper> </View> <KeyboardStickyView> <View style={{ borderTopWidth: 1, borderTopColor: borderColor, paddingBottom: controlsBottomPadding, }} > {flow.attachments.length > 0 ? ( <View style={{ paddingHorizontal: 16, paddingTop: 12 }}> <ComposerAttachmentStrip attachments={flow.attachments} onRemove={flow.removeAttachment} imageSize={88} imageBorderRadius={20} /> </View> ) : null} <View className="flex-row items-center justify-between gap-2 px-4 pt-2"> <ControlPill icon="plus" onPress={() => void handlePickImages()} /> <ControlPillMenu actions={modelMenuActions} onPressAction={({ nativeEvent }) => handleModelMenuAction(nativeEvent.event)} > <ControlPill iconNode={ <ProviderIcon provider={flow.selectedModelOption?.providerDriver} size={16} /> } /> </ControlPillMenu> <ControlPillMenu actions={optionsMenuActions} onPressAction={({ nativeEvent }) => handleOptionsMenuAction(nativeEvent.event)} > <ControlPill icon="slider.horizontal.3" /> </ControlPillMenu> <ControlPillMenu actions={environmentMenuActions} onPressAction={({ nativeEvent }) => handleEnvironmentMenuAction(nativeEvent.event)} > <ControlPill icon="desktopcomputer" /> </ControlPillMenu> <ControlPillMenu actions={workspaceMenuActions} onPressAction={({ nativeEvent }) => handleWorkspaceMenuAction(nativeEvent.event)} > <ControlPill icon="point.topleft.down.curvedto.point.bottomright.up" /> </ControlPillMenu> <ControlPill icon="arrow.up" label={flow.submitting ? "Starting" : "Start"} onPress={() => void handleStart()} variant="primary" disabled={ !flow.selectedProject || !flow.selectedModel || flow.prompt.trim().length === 0 || flow.submitting || (flow.workspaceMode === "worktree" && !flow.selectedBranchName) } /> </View> </View> </KeyboardStickyView> </View> );}
import { useRouter } from "expo-router";
import { TextInputWrapper } from "expo-paste-input";
import { useCallback, useEffect, useMemo } from "react";
import { View } from "react-native";
import { KeyboardStickyView, useKeyboardState } from "react-native-keyboard-controller";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { useThemeColor } from "../../lib/useThemeColor";
import { EnvironmentId, type ModelSelection } from "@t3tools/contracts";
import { AppTextInput as TextInput } from "../../components/AppText";
import { ComposerAttachmentStrip } from "../../components/ComposerAttachmentStrip";
import { ControlPill, ControlPillMenu } from "../../components/ControlPill";
import { ProviderIcon } from "../../components/ProviderIcon";
import { convertPastedImagesToAttachments, pickComposerImages } from "../../lib/composerImages";
import { buildThreadRoutePath } from "../../lib/routes";
import { useRemoteCatalog } from "../../state/use-remote-catalog";
import { useNativePaste } from "../../lib/useNativePaste";
import { CLAUDE_AGENT_EFFORT_OPTIONS } from "./claudeEffortOptions";
import { NewTaskSheetHeader } from "./NewTaskSheetHeader";
import { branchBadgeLabel, useNewTaskFlow } from "./new-task-flow-provider";
import { useProjectActions } from "./use-project-actions";
function withModelSelectionOption(
selection: ModelSelection,
id: string,
value: string | boolean | undefined,
): ModelSelection {
const options = (selection.options ?? []).filter((option) => option.id !== id);
return {
...selection,
options: value === undefined ? options : [...options, { id, value }],
};
}
export function NewTaskDraftScreen(props: {
readonly initialProjectRef?: {
readonly environmentId?: string;
readonly projectId?: string;
};
}) {
const { projects } = useRemoteCatalog();
const { onCreateThreadWithOptions } = useProjectActions();
const flow = useNewTaskFlow();
const router = useRouter();
const insets = useSafeAreaInsets();
const isKeyboardVisible = useKeyboardState((state) => state.isVisible);
const controlsBottomPadding = isKeyboardVisible ? 8 : Math.max(insets.bottom, 10);
const { logicalProjects, selectedProject, setProject } = flow;
const borderColor = useThemeColor("--color-border");
useEffect(() => {
if (props.initialProjectRef?.environmentId && props.initialProjectRef?.projectId) {
const directProject =
projects.find(
(project) =>
project.environmentId === props.initialProjectRef?.environmentId &&
project.id === props.initialProjectRef?.projectId,
) ?? null;
if (directProject) {
setProject(directProject);
return;
}
}
if (selectedProject) {
return;
}
if (logicalProjects.length === 1) {
setProject(logicalProjects[0]!.project);
return;
}
router.replace("/new");
}, [
logicalProjects,
projects,
props.initialProjectRef?.environmentId,
props.initialProjectRef?.projectId,
router,
selectedProject,
setProject,
]);
useEffect(() => {
if (!selectedProject) {
return;
}
void flow.loadBranches();
}, [flow, selectedProject]);
const environmentMenuActions = useMemo(
() =>
flow.environments.map((environment) => ({
id: `environment:${environment.environmentId}`,
title: environment.environmentLabel,
state:
flow.selectedEnvironmentId === environment.environmentId ? ("on" as const) : undefined,
})),
[flow.environments, flow.selectedEnvironmentId],
);
const modelMenuActions = useMemo(
() =>
flow.providerGroups.map((group) => ({
id: `provider:${group.providerKey}`,
title: group.providerLabel,
subtitle: group.models.find(
(model) =>
flow.selectedModel &&
model.selection.instanceId === flow.selectedModel.instanceId &&
model.selection.model === flow.selectedModel.model,
)?.label,
subactions: group.models.map((option) => ({
id: `model:${option.key}`,
title: option.label,
state:
flow.selectedModel &&
option.selection.instanceId === flow.selectedModel.instanceId &&
option.selection.model === flow.selectedModel.model
? ("on" as const)
: undefined,
})),
})),
[flow.providerGroups, flow.selectedModel],
);
const optionsMenuActions = useMemo(
() => [
{
id: "options-effort",
title: "Effort",
subtitle: `${flow.effort.charAt(0).toUpperCase()}${flow.effort.slice(1)}`,
subactions: CLAUDE_AGENT_EFFORT_OPTIONS.map((level) => ({
id: `options:effort:${level}`,
title: `${level}${level === "high" ? " (default)" : ""}`,
state: flow.effort === level ? ("on" as const) : undefined,
})),
},
{
id: "options-fast-mode",
title: "Fast Mode",
subtitle: flow.fastMode ? "On" : "Off",
subactions: ([false, true] as const).map((value) => ({
id: `options:fast-mode:${value ? "on" : "off"}`,
title: value ? "On" : "Off",
state: flow.fastMode === value ? ("on" as const) : undefined,
})),
},
{
id: "options-context-window",
title: "Context Window",
subtitle: flow.contextWindow,
subactions: (["200k", "1M"] as const).map((value) => ({
id: `options:context-window:${value}`,
title: `${value}${value === "1M" ? " (default)" : ""}`,
state: flow.contextWindow === value ? ("on" as const) : undefined,
})),
},
{
id: "options-runtime",
title: "Runtime",
subtitle:
flow.runtimeMode === "approval-required"
? "Approve actions"
: flow.runtimeMode === "auto-accept-edits"
? "Auto-accept edits"
: "Full access",
subactions: [
{ id: "options:runtime:approval-required", title: "Approve actions" },
{ id: "options:runtime:auto-accept-edits", title: "Auto-accept edits" },
{ id: "options:runtime:full-access", title: "Full access" },
].map((option) => {
const value = option.id.replace("options:runtime:", "");
return {
id: option.id,
title: option.title,
state: flow.runtimeMode === value ? ("on" as const) : undefined,
};
}),
},
{
id: "options-interaction",
title: "Interaction",
subtitle: flow.interactionMode === "plan" ? "Plan" : "Default",
subactions: [
{ id: "options:interaction:default", title: "Default" },
{ id: "options:interaction:plan", title: "Plan" },
].map((option) => {
const value = option.id.replace("options:interaction:", "");
return {
id: option.id,
title: option.title,
state: flow.interactionMode === value ? ("on" as const) : undefined,
};
}),
},
],
[flow.contextWindow, flow.effort, flow.fastMode, flow.interactionMode, flow.runtimeMode],
);
const workspaceMenuActions = useMemo(() => {
const branchActions =
flow.availableBranches.length === 0
? [
{
id: "workspace:branch:none",
title: flow.branchesLoading ? "Loading branches…" : "No branches available",
attributes: { disabled: true },
},
]
: flow.availableBranches.slice(0, 12).map((branch) => {
const badge = branchBadgeLabel({
branch,
project: flow.selectedProject,
});
return {
id: `workspace:branch:${branch.name}`,
title: branch.name,
subtitle: badge ? badge.toUpperCase() : undefined,
state: flow.selectedBranchName === branch.name ? ("on" as const) : undefined,
};
});
return [
{
id: "workspace:mode",
title: "Mode",
subtitle: flow.workspaceMode === "local" ? "Local" : "Worktree",
subactions: (["local", "worktree"] as const).map((value) => ({
id: `workspace:mode:${value}`,
title: value === "local" ? "Local" : "Worktree",
state: flow.workspaceMode === value ? ("on" as const) : undefined,
})),
},
{
id: "workspace:branch",
title: "Branch",
subtitle: flow.selectedBranchName ?? "Choose branch",
subactions: branchActions,
},
];
}, [
flow.availableBranches,
flow.branchesLoading,
flow.selectedBranchName,
flow.selectedProject,
flow.workspaceMode,
]);
function handleModelMenuAction(event: string) {
if (!event.startsWith("model:")) {
return;
}
// Defer state update so the native menu dismiss animation completes
// before re-rendering the menu actions (prevents submenu jump).
setTimeout(() => {
flow.setSelectedModelKey(event.slice("model:".length));
}, 150);
}
function handleEnvironmentMenuAction(event: string) {
if (!event.startsWith("environment:")) {
return;
}
flow.selectEnvironment(EnvironmentId.make(event.slice("environment:".length)));
}
function handleOptionsMenuAction(event: string) {
if (event.startsWith("options:effort:")) {
flow.setEffort(event.slice("options:effort:".length) as typeof flow.effort);
return;
}
if (event.startsWith("options:fast-mode:")) {
flow.setFastMode(event.endsWith(":on"));
return;
}
if (event.startsWith("options:context-window:")) {
flow.setContextWindow(event.slice("options:context-window:".length));
return;
}
if (event.startsWith("options:runtime:")) {
flow.setRuntimeMode(
event.slice("options:runtime:".length) as Parameters<typeof flow.setRuntimeMode>[0],
);
return;
}
if (event.startsWith("options:interaction:")) {
flow.setInteractionMode(
event.slice("options:interaction:".length) as Parameters<typeof flow.setInteractionMode>[0],
);
}
}
function handleWorkspaceMenuAction(event: string) {
if (event.startsWith("workspace:mode:")) {
flow.setWorkspaceMode(
event.slice("workspace:mode:".length) as Parameters<typeof flow.setWorkspaceMode>[0],
);
return;
}
if (event.startsWith("workspace:branch:")) {
const branchName = event.slice("workspace:branch:".length);
const branch = flow.availableBranches.find((candidate) => candidate.name === branchName);
if (branch) {
flow.selectBranch(branch);
}
}
}
async function handlePickImages(): Promise<void> {
const result = await pickComposerImages({ existingCount: flow.attachments.length });
if (result.images.length > 0) {
flow.appendAttachments(result.images);
}
}
const handleNativePasteImages = useCallback(
async (uris: ReadonlyArray<string>) => {
try {
const images = await convertPastedImagesToAttachments({
uris,
existingCount: flow.attachments.length,
});
if (images.length > 0) {
flow.appendAttachments(images);
}
} catch (error) {
console.error("[native paste] error converting images", error);
}
},
[flow],
);
const handleNativePaste = useNativePaste((uris) => {
void handleNativePasteImages(uris);
});
async function handleStart(): Promise<void> {
if (
!flow.selectedProject ||
!flow.selectedModel ||
flow.prompt.trim().length === 0 ||
flow.submitting ||
(flow.workspaceMode === "worktree" && !flow.selectedBranchName)
) {
return;
}
flow.setSubmitting(true);
try {
const modelWithOptions: ModelSelection =
flow.selectedModelOption?.providerDriver === "claudeAgent"
? withModelSelectionOption(
withModelSelectionOption(
withModelSelectionOption(flow.selectedModel, "effort", flow.effort),
"fastMode",
flow.fastMode || undefined,
),
"contextWindow",
flow.contextWindow,
)
: flow.selectedModelOption?.providerDriver === "codex"
? withModelSelectionOption(flow.selectedModel, "fastMode", flow.fastMode || undefined)
: flow.selectedModel;
const createdThread = await onCreateThreadWithOptions({
project: flow.selectedProject,
modelSelection: modelWithOptions,
envMode: flow.workspaceMode,
branch: flow.selectedBranchName,
worktreePath: flow.workspaceMode === "worktree" ? null : flow.selectedWorktreePath,
runtimeMode: flow.runtimeMode,
interactionMode: flow.interactionMode,
initialMessageText: flow.prompt.trim(),
initialAttachments: flow.attachments,
});
if (createdThread) {
router.replace(buildThreadRoutePath(createdThread));
}
} finally {
flow.setSubmitting(false);
}
}
if (!selectedProject) {
return (
<View className="flex-1 bg-sheet">
<NewTaskSheetHeader title="Loading task" />
</View>
);
}
return (
<View className="flex-1 bg-sheet">
<NewTaskSheetHeader
title={selectedProject.title}
control={
flow.logicalProjects.length > 1
? { icon: "chevron.left", onPress: () => router.back() }
: undefined
}
/>
<View style={{ flex: 1, paddingHorizontal: 20, paddingTop: 8 }}>
<TextInputWrapper
onPaste={(payload) => void handleNativePaste(payload)}
style={{ flex: 1 }}
>
<TextInput
multiline
value={flow.prompt}
onChangeText={flow.setPrompt}
placeholder={`Describe a coding task in ${selectedProject.title}`}
textAlignVertical="top"
className="h-full flex-1 border-0 bg-transparent text-[18px] leading-[28px]"
style={{ flex: 1 }}
/>
</TextInputWrapper>
</View>
<KeyboardStickyView>
<View
style={{
borderTopWidth: 1,
borderTopColor: borderColor,
paddingBottom: controlsBottomPadding,
}}
>
{flow.attachments.length > 0 ? (
<View style={{ paddingHorizontal: 16, paddingTop: 12 }}>
<ComposerAttachmentStrip
attachments={flow.attachments}
onRemove={flow.removeAttachment}
imageSize={88}
imageBorderRadius={20}
/>
</View>
) : null}
<View className="flex-row items-center justify-between gap-2 px-4 pt-2">
<ControlPill icon="plus" onPress={() => void handlePickImages()} />
<ControlPillMenu
actions={modelMenuActions}
onPressAction={({ nativeEvent }) => handleModelMenuAction(nativeEvent.event)}
>
<ControlPill
iconNode={
<ProviderIcon provider={flow.selectedModelOption?.providerDriver} size={16} />
}
/>
</ControlPillMenu>
<ControlPillMenu
actions={optionsMenuActions}
onPressAction={({ nativeEvent }) => handleOptionsMenuAction(nativeEvent.event)}
>
<ControlPill icon="slider.horizontal.3" />
</ControlPillMenu>
<ControlPillMenu
actions={environmentMenuActions}
onPressAction={({ nativeEvent }) => handleEnvironmentMenuAction(nativeEvent.event)}
>
<ControlPill icon="desktopcomputer" />
</ControlPillMenu>
<ControlPillMenu
actions={workspaceMenuActions}
onPressAction={({ nativeEvent }) => handleWorkspaceMenuAction(nativeEvent.event)}
>
<ControlPill icon="point.topleft.down.curvedto.point.bottomright.up" />
</ControlPillMenu>
<ControlPill
icon="arrow.up"
label={flow.submitting ? "Starting" : "Start"}
onPress={() => void handleStart()}
variant="primary"
disabled={
!flow.selectedProject ||
!flow.selectedModel ||
flow.prompt.trim().length === 0 ||
flow.submitting ||
(flow.workspaceMode === "worktree" && !flow.selectedBranchName)
}
/>
</View>
</View>
</KeyboardStickyView>
</View>
);
}