Skip to content

Commit 4ea013b

Browse files
authored
Merge branch 'main' into staging/reject-userinfo-advertised-endpoints
2 parents 0936358 + 6672a1d commit 4ea013b

54 files changed

Lines changed: 1737 additions & 510 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/desktop/src/settings/DesktopClientSettings.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ const clientSettings: ClientSettings = {
1818
confirmThreadDelete: false,
1919
dismissedProviderUpdateNotificationKeys: [],
2020
diffIgnoreWhitespace: true,
21-
diffWordWrap: true,
2221
favorites: [],
2322
providerModelPreferences: {},
2423
sidebarProjectGroupingMode: "repository_path",
@@ -29,6 +28,7 @@ const clientSettings: ClientSettings = {
2928
sidebarThreadSortOrder: "created_at",
3029
sidebarThreadPreviewCount: 6,
3130
timestampFormat: "24-hour",
31+
wordWrap: true,
3232
};
3333

3434
const decodeClientSettingsJson = Schema.decodeEffect(Schema.fromJsonString(ClientSettingsSchema));

apps/mobile/src/features/threads/NewTaskDraftScreen.tsx

Lines changed: 45 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@ import {
3030
resolveProviderOptionDescriptors,
3131
} from "../../lib/providerOptions";
3232
import { buildThreadRoutePath } from "../../lib/routes";
33+
import { scopedProjectKey } from "../../lib/scopedEntities";
3334
import { MOBILE_TYPOGRAPHY } from "../../lib/typography";
35+
import { getComposerDraftSnapshot } from "../../state/use-composer-drafts";
3436
import { useProjects } from "../../state/entities";
3537
import { branchBadgeLabel, useNewTaskFlow } from "./new-task-flow-provider";
3638
import { useCreateProjectThread } from "./use-project-actions";
@@ -63,6 +65,7 @@ export function NewTaskDraftScreen(props: {
6365
const controlsBottomPadding = isKeyboardVisible ? 8 : Math.max(insets.bottom, 10);
6466
const { logicalProjects, selectedProject, setProject } = flow;
6567
const promptInputRef = useRef<ComposerEditorHandle>(null);
68+
const loadedBranchesProjectKeyRef = useRef<string | null>(null);
6669

6770
const borderColor = useThemeColor("--color-border");
6871
const sheetFadeOpaque = colorScheme === "dark" ? "rgba(14,14,14,0.98)" : "rgba(242,242,247,0.98)";
@@ -78,6 +81,12 @@ export function NewTaskDraftScreen(props: {
7881
) ?? null;
7982

8083
if (directProject) {
84+
if (
85+
selectedProject?.environmentId === directProject.environmentId &&
86+
selectedProject.id === directProject.id
87+
) {
88+
return;
89+
}
8190
setProject(directProject);
8291
return;
8392
}
@@ -105,10 +114,16 @@ export function NewTaskDraftScreen(props: {
105114

106115
useEffect(() => {
107116
if (!selectedProject) {
117+
loadedBranchesProjectKeyRef.current = null;
118+
return;
119+
}
120+
const projectKey = `${selectedProject.environmentId}:${selectedProject.id}`;
121+
if (loadedBranchesProjectKeyRef.current === projectKey) {
108122
return;
109123
}
124+
loadedBranchesProjectKeyRef.current = projectKey;
110125
void flow.loadBranches();
111-
}, [flow, selectedProject]);
126+
}, [flow.loadBranches, selectedProject]);
112127

113128
useEffect(() => {
114129
if (!selectedProject) {
@@ -292,11 +307,7 @@ export function NewTaskDraftScreen(props: {
292307
if (!event.startsWith("model:")) {
293308
return;
294309
}
295-
// Defer state update so the native menu dismiss animation completes
296-
// before re-rendering the menu actions (prevents submenu jump).
297-
setTimeout(() => {
298-
flow.setSelectedModelKey(event.slice("model:".length));
299-
}, 150);
310+
flow.setSelectedModelKey(event.slice("model:".length));
300311
}
301312

302313
function handleEnvironmentMenuAction(event: string) {
@@ -366,27 +377,42 @@ export function NewTaskDraftScreen(props: {
366377
);
367378

368379
async function handleStart(): Promise<void> {
380+
const selectedProject = flow.selectedProject;
381+
if (!selectedProject) {
382+
return;
383+
}
384+
const draft = getComposerDraftSnapshot(
385+
`new-task:${scopedProjectKey(selectedProject.environmentId, selectedProject.id)}`,
386+
);
387+
const modelSelection = draft.modelSelection ?? flow.selectedModel;
388+
const workspaceMode = draft.workspaceSelection?.mode ?? flow.workspaceMode;
389+
const selectedBranchName = draft.workspaceSelection?.branch ?? flow.selectedBranchName;
390+
const selectedWorktreePath =
391+
draft.workspaceSelection?.worktreePath ?? flow.selectedWorktreePath;
392+
const runtimeMode = draft.runtimeMode ?? flow.runtimeMode;
393+
const interactionMode = draft.interactionMode ?? flow.interactionMode;
394+
const initialMessageText = draft.text.trim();
395+
369396
if (
370-
!flow.selectedProject ||
371-
!flow.selectedModel ||
372-
flow.prompt.trim().length === 0 ||
397+
!modelSelection ||
398+
initialMessageText.length === 0 ||
373399
flow.submitting ||
374-
(flow.workspaceMode === "worktree" && !flow.selectedBranchName)
400+
(workspaceMode === "worktree" && !selectedBranchName)
375401
) {
376402
return;
377403
}
378404

379405
flow.setSubmitting(true);
380406
const result = await createProjectThread({
381-
project: flow.selectedProject,
382-
modelSelection: flow.selectedModel,
383-
envMode: flow.workspaceMode,
384-
branch: flow.selectedBranchName,
385-
worktreePath: flow.workspaceMode === "worktree" ? null : flow.selectedWorktreePath,
386-
runtimeMode: flow.runtimeMode,
387-
interactionMode: flow.interactionMode,
388-
initialMessageText: flow.prompt.trim(),
389-
initialAttachments: flow.attachments,
407+
project: selectedProject,
408+
modelSelection,
409+
envMode: workspaceMode,
410+
branch: selectedBranchName,
411+
worktreePath: workspaceMode === "worktree" ? null : selectedWorktreePath,
412+
runtimeMode,
413+
interactionMode,
414+
initialMessageText,
415+
initialAttachments: draft.attachments,
390416
});
391417
flow.setSubmitting(false);
392418

apps/mobile/src/features/threads/ThreadRouteScreen.tsx

Lines changed: 17 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,7 @@
11
import { Stack, useLocalSearchParams, useRouter } from "expo-router";
22
import { useCallback, useMemo, useState } from "react";
33
import * as Option from "effect/Option";
4-
import {
5-
EnvironmentId,
6-
type ModelSelection,
7-
type ProjectScript,
8-
type ProviderInteractionMode,
9-
type RuntimeMode,
10-
} from "@t3tools/contracts";
4+
import { EnvironmentId, type ProjectScript } from "@t3tools/contracts";
115
import { projectScriptCwd, projectScriptRuntimeEnv } from "@t3tools/shared/projectScripts";
126
import { Pressable, ScrollView, Text as RNText, View } from "react-native";
137
import { useWorkspaceState } from "../../state/workspace";
@@ -79,18 +73,6 @@ export function ThreadRouteScreen() {
7973
const gitState = useSelectedThreadGitState();
8074
const gitActions = useSelectedThreadGitActions();
8175
const requests = useSelectedThreadRequests();
82-
const updateThreadMetadata = useAtomCommand(
83-
threadEnvironment.updateMetadata,
84-
"thread metadata update",
85-
);
86-
const setThreadRuntimeMode = useAtomCommand(
87-
threadEnvironment.setRuntimeMode,
88-
"thread runtime mode",
89-
);
90-
const setThreadInteractionMode = useAtomCommand(
91-
threadEnvironment.setInteractionMode,
92-
"thread interaction mode",
93-
);
9476
const interruptThreadTurn = useAtomCommand(threadEnvironment.interruptTurn, "thread interrupt");
9577
const router = useRouter();
9678
const params = useLocalSearchParams<{
@@ -105,6 +87,18 @@ export function ThreadRouteScreen() {
10587
const routeConnectionState =
10688
routeEnvironmentRuntime?.connectionState ?? (environmentId ? "available" : connectionState);
10789
const routeConnectionError = routeEnvironmentRuntime?.connectionError ?? null;
90+
const selectedThreadWithDraftSettings = useMemo(
91+
() =>
92+
selectedThread
93+
? {
94+
...selectedThread,
95+
modelSelection: composer.modelSelection ?? selectedThread.modelSelection,
96+
runtimeMode: composer.runtimeMode ?? selectedThread.runtimeMode,
97+
interactionMode: composer.interactionMode ?? selectedThread.interactionMode,
98+
}
99+
: null,
100+
[composer.interactionMode, composer.modelSelection, composer.runtimeMode, selectedThread],
101+
);
108102

109103
/* ─── Native header theming ──────────────────────────────────────── */
110104
const iconColor = String(useThemeColor("--color-icon"));
@@ -157,51 +151,6 @@ export function ThreadRouteScreen() {
157151
const handleOpenConnectionEditor = useCallback(() => {
158152
void router.push("/connections");
159153
}, [router]);
160-
const handleUpdateThreadModelSelection = useCallback(
161-
(modelSelection: ModelSelection) => {
162-
if (!selectedThread) {
163-
return;
164-
}
165-
return updateThreadMetadata({
166-
environmentId: selectedThread.environmentId,
167-
input: {
168-
threadId: selectedThread.id,
169-
modelSelection,
170-
},
171-
});
172-
},
173-
[selectedThread, updateThreadMetadata],
174-
);
175-
const handleUpdateThreadRuntimeMode = useCallback(
176-
(runtimeMode: RuntimeMode) => {
177-
if (!selectedThread) {
178-
return;
179-
}
180-
return setThreadRuntimeMode({
181-
environmentId: selectedThread.environmentId,
182-
input: {
183-
threadId: selectedThread.id,
184-
runtimeMode,
185-
},
186-
});
187-
},
188-
[selectedThread, setThreadRuntimeMode],
189-
);
190-
const handleUpdateThreadInteractionMode = useCallback(
191-
(interactionMode: ProviderInteractionMode) => {
192-
if (!selectedThread) {
193-
return;
194-
}
195-
return setThreadInteractionMode({
196-
environmentId: selectedThread.environmentId,
197-
input: {
198-
threadId: selectedThread.id,
199-
interactionMode,
200-
},
201-
});
202-
},
203-
[selectedThread, setThreadInteractionMode],
204-
);
205154
const handleStopThread = useCallback(() => {
206155
if (
207156
!selectedThread ||
@@ -435,7 +384,7 @@ export function ThreadRouteScreen() {
435384

436385
<View className="flex-1 bg-screen">
437386
<ThreadDetailScreen
438-
selectedThread={selectedThread}
387+
selectedThread={selectedThreadWithDraftSettings ?? selectedThread}
439388
contentPresentation={contentPresentation}
440389
screenTone={connectionTone(routeConnectionState)}
441390
connectionError={routeConnectionError}
@@ -466,9 +415,9 @@ export function ThreadRouteScreen() {
466415
onStopThread={handleStopThread}
467416
onSendMessage={composer.onSendMessage}
468417
onReconnectEnvironment={handleReconnectEnvironment}
469-
onUpdateThreadModelSelection={handleUpdateThreadModelSelection}
470-
onUpdateThreadRuntimeMode={handleUpdateThreadRuntimeMode}
471-
onUpdateThreadInteractionMode={handleUpdateThreadInteractionMode}
418+
onUpdateThreadModelSelection={composer.onUpdateModelSelection}
419+
onUpdateThreadRuntimeMode={composer.onUpdateRuntimeMode}
420+
onUpdateThreadInteractionMode={composer.onUpdateInteractionMode}
472421
onRespondToApproval={requests.onRespondToApproval}
473422
onSelectUserInputOption={requests.onSelectUserInputOption}
474423
onChangeUserInputCustomAnswer={requests.onChangeUserInputCustomAnswer}

0 commit comments

Comments
 (0)