Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions apps/mobile/src/Stack.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import { NewTaskDraftRouteScreen } from "./features/threads/NewTaskDraftRouteScr
import {
NewTaskBranchPickerRouteScreen,
NewTaskEnvironmentPickerRouteScreen,
NewTaskWorkspacePickerRouteScreen,
} from "./features/threads/NewTaskContextPickerScreens";
import {
ExistingThreadSettingsRouteProvider,
Expand Down Expand Up @@ -273,6 +274,13 @@ const NewTaskSheetStack = createNativeStackNavigator({
title: "Environment",
},
}),
NewTaskWorkspace: createNativeStackScreen({
screen: NewTaskWorkspacePickerRouteScreen,
linking: "draft/workspace",
options: {
title: "Workspace",
},
}),
NewTaskBranch: createNativeStackScreen({
screen: NewTaskBranchPickerRouteScreen,
linking: "draft/branch",
Expand Down
103 changes: 99 additions & 4 deletions apps/mobile/src/features/threads/NewTaskContextPickerScreens.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { VcsRef } from "@t3tools/client-runtime/state/vcs";
import { vcsWorktreeLabel, type VcsRef } from "@t3tools/client-runtime/state/vcs";
import { LegendList } from "@legendapp/list/react-native";
import {
isAtomCommandInterrupted,
Expand Down Expand Up @@ -27,6 +27,7 @@ import { useFontFamily } from "../../lib/useFontFamily";
import { useThemeColor } from "../../lib/useThemeColor";
import { NativeHeaderToolbar, NativeStackScreenOptions } from "../../native/StackHeader";
import { useAtomCommand } from "../../state/use-atom-command";
import { useEnvironmentQuery } from "../../state/query";
import { vcsEnvironment } from "../../state/vcs";
import {
createNativeMailSearchToolbarItem,
Expand Down Expand Up @@ -141,6 +142,91 @@ function PickerSurface(props: { readonly children: ReactNode }) {
return <View className="overflow-hidden rounded-2xl bg-card">{props.children}</View>;
}

export function NewTaskWorkspacePickerRouteScreen() {
const flow = useNewTaskFlow();
const navigation = useNavigation();
const insets = useSafeAreaInsets();
const worktreesQuery = useEnvironmentQuery(
flow.selectedProject
? vcsEnvironment.listWorktrees({
environmentId: flow.selectedProject.environmentId,
input: { cwd: flow.selectedProject.workspaceRoot },
})
: null,
);
const worktrees = worktreesQuery.data?.worktrees ?? [];

return (
<View className="flex-1 bg-sheet" collapsable={false}>
<NativeStackScreenOptions
options={{ headerShown: Platform.OS !== "android", title: "Workspace" }}
/>
{Platform.OS === "android" ? (
<AndroidScreenHeader title="Workspace" onBack={() => navigation.goBack()} />
) : null}
<ScrollView
contentInsetAdjustmentBehavior="automatic"
contentContainerStyle={{
paddingBottom: Math.max(insets.bottom, 16) + 16,
paddingHorizontal: 16,
paddingTop: 16,
}}
showsVerticalScrollIndicator={false}
>
<PickerSurface>
<SelectionRow
icon="desktopcomputer"
onPress={() => {
void Haptics.selectionAsync();
flow.setWorkspaceMode("local");
navigation.goBack();
}}
selected={flow.workspaceMode === "local" && flow.selectedWorktreePath === null}
title="Current checkout"
/>
<SelectionRow
icon="arrow.triangle.branch"
isLast
onPress={() => {
void Haptics.selectionAsync();
flow.setWorkspaceMode("worktree");
navigation.goBack();
}}
selected={flow.workspaceMode === "worktree"}
title="New worktree"
/>
</PickerSurface>
{worktrees.length > 0 ? (
<View className="mt-6 gap-2">
<Text className="px-1 text-xs font-t3-medium uppercase text-foreground-muted">
Worktrees
</Text>
<PickerSurface>
{worktrees.map((worktree, index) => (
<SelectionRow
key={worktree.path}
icon="arrow.triangle.branch"
isLast={index === worktrees.length - 1}
onPress={() => {
void Haptics.selectionAsync();
flow.selectWorktree(worktree);
navigation.goBack();
}}
selected={
flow.workspaceMode === "local" && flow.selectedWorktreePath === worktree.path
}
subtitle={worktree.path}
title={vcsWorktreeLabel(worktree)}
/>
))}
</PickerSurface>
</View>
) : null}
</ScrollView>
</View>
);
}

export function NewTaskEnvironmentPickerRouteScreen() {
const flow = useNewTaskFlow();
const navigation = useNavigation();
Expand Down Expand Up @@ -194,7 +280,9 @@ export function NewTaskBranchPickerRouteScreen() {
const placeholderColor = useThemeColor("--color-placeholder");
const foregroundColor = useThemeColor("--color-foreground");
const fontFamily = useFontFamily("regular");
const switchRef = useAtomCommand(vcsEnvironment.switchRef, { reportFailure: false });
const switchRef = useAtomCommand(vcsEnvironment.switchRef, {
reportFailure: false,
});
const [switchingBranchName, setSwitchingBranchName] = useState<string | null>(null);
const selectingBranchNameRef = useRef<string | null>(null);
const allowSelectionNavigationRef = useRef(false);
Expand Down Expand Up @@ -310,7 +398,10 @@ export function NewTaskBranchPickerRouteScreen() {
const renderBranch = useCallback(
({ item, index }: { readonly item: VcsRef; readonly index: number }) => (
<BranchSelectionRow
badge={branchBadgeLabel({ branch: item, project: flow.selectedProject })}
badge={branchBadgeLabel({
branch: item,
project: flow.selectedProject,
})}
branch={item}
disabled={switchingBranchName !== null}
isFirst={index === 0}
Expand Down Expand Up @@ -344,7 +435,11 @@ export function NewTaskBranchPickerRouteScreen() {
<ScrollView
className="flex-1 bg-sheet"
contentInsetAdjustmentBehavior="automatic"
contentContainerStyle={{ flexGrow: 1, paddingHorizontal: 16, paddingTop: 12 }}
contentContainerStyle={{
flexGrow: 1,
paddingHorizontal: 16,
paddingTop: 12,
}}
scrollEnabled={false}
showsVerticalScrollIndicator={false}
>
Expand Down
26 changes: 19 additions & 7 deletions apps/mobile/src/features/threads/NewTaskDraftScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,9 @@ export function NewTaskDraftScreen(props: {
// route, otherwise the transfer guard can swallow the fallback action.
const frame = requestAnimationFrame(() => {
navigation.dispatch(
StackActions.replace("NewTask", { incomingShareId: props.incomingShareId }),
StackActions.replace("NewTask", {
incomingShareId: props.incomingShareId,
}),
);
});
return () => cancelAnimationFrame(frame);
Expand Down Expand Up @@ -596,6 +598,7 @@ export function NewTaskDraftScreen(props: {
selectedBranch:
flow.selectedBranchName ??
(flow.workspaceMode === "worktree" ? availableCurrentBranchName : null),
selectedWorktreePath: flow.selectedWorktreePath,
currentCheckoutBranch: flow.currentCheckoutBranchName,
});
const selectedBranchLabel = resolveNewTaskBranchLabel({
Expand All @@ -613,7 +616,9 @@ export function NewTaskDraftScreen(props: {
if (isIncomingShareTransferPending) {
return;
}
const result = await pickComposerImages({ existingCount: flow.attachments.length });
const result = await pickComposerImages({
existingCount: flow.attachments.length,
});
if (result.images.length > 0) {
flow.appendAttachments(result.images);
}
Expand Down Expand Up @@ -726,6 +731,7 @@ export function NewTaskDraftScreen(props: {
const creationBranch = resolveProjectThreadCreationBranch({
workspaceMode,
selectedBranch: selectedBranchName,
selectedWorktreePath,
currentCheckoutBranch: flow.currentCheckoutBranchName,
});
const result = await createProjectThread({
Expand Down Expand Up @@ -830,7 +836,11 @@ export function NewTaskDraftScreen(props: {
paddingHorizontal: 4,
paddingVertical: 4,
}}
textStyle={{ ...bodyText, color: foregroundColor, fontFamily: regularFontFamily }}
textStyle={{
...bodyText,
color: foregroundColor,
fontFamily: regularFontFamily,
}}
/>
);

Expand All @@ -851,7 +861,9 @@ export function NewTaskDraftScreen(props: {
void KeyboardController.dismiss({ animated: true });
navigation.dispatch(StackActions.push("NewTask", { incomingShareId: props.incomingShareId }));
};
const openContextPicker = (routeName: "NewTaskBranch" | "NewTaskEnvironment") => {
const openContextPicker = (
routeName: "NewTaskBranch" | "NewTaskEnvironment" | "NewTaskWorkspace",
) => {
if (isIncomingShareTransferPending) {
return;
}
Expand Down Expand Up @@ -927,7 +939,7 @@ export function NewTaskDraftScreen(props: {
const workspaceControls = (
<View className="flex-row items-center gap-1 px-2">
<ComposerInlineControl
accessibilityHint={`Switches to ${flow.workspaceMode === "local" ? "a new worktree" : "the current checkout"}`}
accessibilityHint="Opens the workspace picker"
accessibilityLabel={workspaceLabel}
disabled={isIncomingShareTransferPending}
iconNode={
Expand All @@ -938,8 +950,8 @@ export function NewTaskDraftScreen(props: {
}
label={workspaceLabel}
maxWidth={flow.workspaceMode === "local" ? 220 : 148}
onPress={() => flow.setWorkspaceMode(flow.workspaceMode === "local" ? "worktree" : "local")}
showChevron={false}
onPress={() => openContextPicker("NewTaskWorkspace")}
showChevron
/>

<ComposerInlineControl
Expand Down
44 changes: 40 additions & 4 deletions apps/mobile/src/features/threads/new-task-flow-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type {
ProviderOptionSelection,
RuntimeMode,
ServerProviderSkill,
VcsWorktree,
} from "@t3tools/contracts";
import {
CommandId,
Expand Down Expand Up @@ -165,6 +166,7 @@ type NewTaskFlowContextValue = {
) => void;
readonly setWorkspaceMode: (mode: WorkspaceMode) => void;
readonly selectBranch: (branch: VcsRef) => void;
readonly selectWorktree: (worktree: VcsWorktree) => void;
readonly setStartFromOrigin: (value: boolean) => void;
readonly beginEditingPendingTask: (messageId: string) => boolean;
readonly finishEditingPendingTask: () => void;
Expand Down Expand Up @@ -367,7 +369,10 @@ export function NewTaskFlowProvider(props: React.PropsWithChildren) {
selectedProject !== null && selectedProject.workspaceRoot !== ""
? projectEnvironment.readFile({
environmentId: selectedProject.environmentId,
input: { cwd: selectedProject.workspaceRoot, relativePath: T3_PROJECT_FILE_NAME },
input: {
cwd: selectedProject.workspaceRoot,
relativePath: T3_PROJECT_FILE_NAME,
},
})
: null,
);
Expand Down Expand Up @@ -652,7 +657,14 @@ export function NewTaskFlowProvider(props: React.PropsWithChildren) {
workspaceSelection: {
mode,
branch: mode === "local" ? localSelection.branch : selectedBranchName,
worktreePath: mode === "local" ? localSelection.worktreePath : selectedWorktreePath,
worktreePath:
mode === "local"
? localSelection.worktreePath
: resolveNewTaskBranchWorktreePath({
workspaceMode: mode,
projectCwd: selectedProject.workspaceRoot,
branchWorktreePath: selectedWorktreePath,
}),
...(draftStartFromOrigin !== undefined ? { startFromOrigin: draftStartFromOrigin } : {}),
},
});
Expand Down Expand Up @@ -723,6 +735,24 @@ export function NewTaskFlowProvider(props: React.PropsWithChildren) {
[draftStartFromOrigin, selectedProject, selectedProjectDraftKey, workspaceMode],
);

const selectWorktree = useCallback(
(worktree: VcsWorktree) => {
if (!selectedProjectDraftKey) {
return;
}
pendingLocalBranchSyncDraftKeysRef.current.delete(selectedProjectDraftKey);
updateComposerDraftSettings(selectedProjectDraftKey, {
workspaceSelection: {
mode: "local",
branch: worktree.branch,
worktreePath: worktree.path,
...(draftStartFromOrigin !== undefined ? { startFromOrigin: draftStartFromOrigin } : {}),
},
});
},
[draftStartFromOrigin, selectedProjectDraftKey],
);

const setStartFromOrigin = useCallback(
(value: boolean) => {
if (!selectedProjectDraftKey) {
Expand Down Expand Up @@ -779,15 +809,19 @@ export function NewTaskFlowProvider(props: React.PropsWithChildren) {
const setRuntimeMode = useCallback(
(value: RuntimeMode) => {
if (selectedProjectDraftKey) {
updateComposerDraftSettings(selectedProjectDraftKey, { runtimeMode: value });
updateComposerDraftSettings(selectedProjectDraftKey, {
runtimeMode: value,
});
}
},
[selectedProjectDraftKey],
);
const setInteractionMode = useCallback(
(value: ProviderInteractionMode) => {
if (selectedProjectDraftKey) {
updateComposerDraftSettings(selectedProjectDraftKey, { interactionMode: value });
updateComposerDraftSettings(selectedProjectDraftKey, {
interactionMode: value,
});
}
},
[selectedProjectDraftKey],
Expand Down Expand Up @@ -1037,6 +1071,7 @@ export function NewTaskFlowProvider(props: React.PropsWithChildren) {
setSelectedModelKey,
setWorkspaceMode,
selectBranch,
selectWorktree,
setStartFromOrigin,
beginEditingPendingTask,
finishEditingPendingTask,
Expand Down Expand Up @@ -1097,6 +1132,7 @@ export function NewTaskFlowProvider(props: React.PropsWithChildren) {
selectedWorktreePath,
setProject,
selectBranch,
selectWorktree,
selectEnvironment,
setInteractionMode,
setPrompt,
Expand Down
Loading
Loading