Skip to content

Commit 6b48288

Browse files
juliusmarmingecodex
andcommitted
Sync mobile remote branch with main
Co-authored-by: codex <codex@users.noreply.github.com>
1 parent 2632c92 commit 6b48288

38 files changed

Lines changed: 801 additions & 250 deletions

apps/mobile/src/features/review/nativeReviewDiffAdapter.ts

Lines changed: 431 additions & 0 deletions
Large diffs are not rendered by default.

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

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,18 @@ import { CLAUDE_AGENT_EFFORT_OPTIONS } from "./claudeEffortOptions";
2323
import { branchBadgeLabel, useNewTaskFlow } from "./new-task-flow-provider";
2424
import { useProjectActions } from "./use-project-actions";
2525

26+
function withModelSelectionOption(
27+
selection: ModelSelection,
28+
id: string,
29+
value: string | boolean | undefined,
30+
): ModelSelection {
31+
const options = (selection.options ?? []).filter((option) => option.id !== id);
32+
return {
33+
...selection,
34+
options: value === undefined ? options : [...options, { id, value }],
35+
};
36+
}
37+
2638
export function NewTaskDraftScreen(props: {
2739
readonly initialProjectRef?: {
2840
readonly environmentId?: string;
@@ -102,15 +114,15 @@ export function NewTaskDraftScreen(props: {
102114
subtitle: group.models.find(
103115
(model) =>
104116
flow.selectedModel &&
105-
model.selection.provider === flow.selectedModel.provider &&
117+
model.selection.instanceId === flow.selectedModel.instanceId &&
106118
model.selection.model === flow.selectedModel.model,
107119
)?.label,
108120
subactions: group.models.map((option) => ({
109121
id: `model:${option.key}`,
110122
title: option.label,
111123
state:
112124
flow.selectedModel &&
113-
option.selection.provider === flow.selectedModel.provider &&
125+
option.selection.instanceId === flow.selectedModel.instanceId &&
114126
option.selection.model === flow.selectedModel.model
115127
? ("on" as const)
116128
: undefined,
@@ -345,17 +357,18 @@ export function NewTaskDraftScreen(props: {
345357
flow.setSubmitting(true);
346358
try {
347359
const modelWithOptions: ModelSelection =
348-
flow.selectedModel.provider === "claudeAgent"
349-
? {
350-
...flow.selectedModel,
351-
options: {
352-
effort: flow.effort,
353-
fastMode: flow.fastMode || undefined,
354-
contextWindow: flow.contextWindow,
355-
},
356-
}
357-
: flow.selectedModel.provider === "codex"
358-
? { ...flow.selectedModel, options: { fastMode: flow.fastMode || undefined } }
360+
flow.selectedModelOption?.providerDriver === "claudeAgent"
361+
? withModelSelectionOption(
362+
withModelSelectionOption(
363+
withModelSelectionOption(flow.selectedModel, "effort", flow.effort),
364+
"fastMode",
365+
flow.fastMode || undefined,
366+
),
367+
"contextWindow",
368+
flow.contextWindow,
369+
)
370+
: flow.selectedModelOption?.providerDriver === "codex"
371+
? withModelSelectionOption(flow.selectedModel, "fastMode", flow.fastMode || undefined)
359372
: flow.selectedModel;
360373

361374
const createdThread = await onCreateThreadWithOptions({

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

Lines changed: 44 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,12 @@ import {
4242
normalizeSearchQuery,
4343
scoreQueryMatch,
4444
} from "@t3tools/shared/searchRanking";
45+
import {
46+
getModelSelectionBooleanOptionValue,
47+
getModelSelectionStringOptionValue,
48+
} from "@t3tools/shared/model";
4549
import { useComposerPathSearch } from "../../state/use-composer-path-search";
46-
import { CLAUDE_AGENT_EFFORT_OPTIONS, type ClaudeAgentEffort } from "./claudeEffortOptions";
50+
import { CLAUDE_AGENT_EFFORT_OPTIONS } from "./claudeEffortOptions";
4751
import { ComposerCommandPopover, type ComposerCommandItem } from "./ComposerCommandPopover";
4852

4953
/**
@@ -65,22 +69,6 @@ export const COMPOSER_EXPANDED_CHROME = 174;
6569
*/
6670
export const COMPOSER_EXPANDED_TOOLBAR_CHROME = 54;
6771

68-
function withModelSelectionOption(
69-
selection: ModelSelection,
70-
id: string,
71-
value: string | boolean | undefined,
72-
): ModelSelection {
73-
const options = (selection.options ?? []).filter((option) => option.id !== id);
74-
if (value !== undefined) {
75-
options.push({ id, value });
76-
}
77-
if (options.length === 0) {
78-
const { options: _options, ...rest } = selection;
79-
return rest as ModelSelection;
80-
}
81-
return { ...selection, options } as ModelSelection;
82-
}
83-
8472
export interface ThreadComposerProps {
8573
readonly draftMessage: string;
8674
readonly draftAttachments: ReadonlyArray<DraftComposerImageAttachment>;
@@ -145,6 +133,18 @@ function ComposerSurface(props: {
145133
);
146134
}
147135

136+
function withModelSelectionOption(
137+
selection: ModelSelection,
138+
id: string,
139+
value: string | boolean | undefined,
140+
): ModelSelection {
141+
const options = (selection.options ?? []).filter((option) => option.id !== id);
142+
return {
143+
...selection,
144+
options: value === undefined ? options : [...options, { id, value }],
145+
};
146+
}
147+
148148
export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposerProps) {
149149
const isDarkMode = useColorScheme() === "dark";
150150
const themePlaceholderColor = useThemeColor("--color-placeholder");
@@ -184,23 +184,30 @@ export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposer
184184
props.queueCount > 0;
185185

186186
const sendLabel = props.activeThreadBusy || props.queueCount > 0 ? "Queue" : "Send";
187-
const modelProvider = props.selectedThread.modelSelection?.provider ?? null;
187+
const modelProvider = props.selectedThread.modelSelection?.instanceId ?? null;
188188
const currentModelSelection = props.selectedThread.modelSelection;
189189
const currentRuntimeMode = props.selectedThread.runtimeMode;
190190
const currentInteractionMode = props.selectedThread.interactionMode ?? "default";
191+
const selectedProviderStatus = useMemo(() => {
192+
if (!props.serverConfig) return null;
193+
return (
194+
props.serverConfig.providers.find(
195+
(p) => p.instanceId === props.selectedThread.modelSelection.instanceId,
196+
) ?? null
197+
);
198+
}, [props.serverConfig, props.selectedThread.modelSelection.instanceId]);
191199

192200
// Extract current model options (effort, fastMode, contextWindow)
201+
const selectedProviderDriver = selectedProviderStatus?.driver ?? null;
193202
const currentEffort =
194-
currentModelSelection.provider === "claudeAgent"
195-
? (currentModelSelection.options?.effort ?? "high")
203+
selectedProviderDriver === "claudeAgent"
204+
? (getModelSelectionStringOptionValue(currentModelSelection, "effort") ?? "high")
196205
: "high";
197206
const currentFastMode =
198-
currentModelSelection.options && "fastMode" in currentModelSelection.options
199-
? (currentModelSelection.options.fastMode ?? false)
200-
: false;
207+
getModelSelectionBooleanOptionValue(currentModelSelection, "fastMode") ?? false;
201208
const currentContextWindow =
202-
currentModelSelection.provider === "claudeAgent"
203-
? (currentModelSelection.options?.contextWindow ?? "1M")
209+
selectedProviderDriver === "claudeAgent"
210+
? (getModelSelectionStringOptionValue(currentModelSelection, "contextWindow") ?? "1M")
204211
: "1M";
205212

206213
const handleNativePaste = useNativePaste((uris) => {
@@ -228,16 +235,6 @@ export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposer
228235
query: composerTrigger?.kind === "path" ? composerTrigger.query : null,
229236
});
230237

231-
// ── Build menu items ─────────────────────────────────────
232-
const selectedProviderStatus = useMemo(() => {
233-
if (!props.serverConfig) return null;
234-
return (
235-
props.serverConfig.providers.find(
236-
(p) => p.provider === props.selectedThread.modelSelection.provider,
237-
) ?? null
238-
);
239-
}, [props.serverConfig, props.selectedThread.modelSelection.provider]);
240-
241238
const composerMenuItems: ComposerCommandItem[] = useMemo(() => {
242239
if (!composerTrigger) return [];
243240

@@ -446,14 +443,14 @@ export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposer
446443
title: group.providerLabel,
447444
subtitle: group.models.find(
448445
(model) =>
449-
model.selection.provider === currentModelSelection.provider &&
446+
model.selection.instanceId === currentModelSelection.instanceId &&
450447
model.selection.model === currentModelSelection.model,
451448
)?.label,
452449
subactions: group.models.map((option) => ({
453450
id: `model:${option.key}`,
454451
title: option.label,
455452
state:
456-
option.selection.provider === currentModelSelection.provider &&
453+
option.selection.instanceId === currentModelSelection.instanceId &&
457454
option.selection.model === currentModelSelection.model
458455
? ("on" as const)
459456
: undefined,
@@ -560,36 +557,31 @@ export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposer
560557
if (event.startsWith("options:effort:")) {
561558
const effort = event.slice("options:effort:".length);
562559
const updated: ModelSelection =
563-
currentModelSelection.provider === "claudeAgent"
564-
? {
565-
...currentModelSelection,
566-
options: { ...currentModelSelection.options, effort: effort as typeof currentEffort },
567-
}
560+
selectedProviderDriver === "claudeAgent"
561+
? withModelSelectionOption(
562+
currentModelSelection,
563+
"effort",
564+
effort as typeof currentEffort,
565+
)
568566
: currentModelSelection;
569567
void props.onUpdateModelSelection(updated);
570568
return;
571569
}
572570
if (event.startsWith("options:fast-mode:")) {
573571
const fastMode = event.endsWith(":on");
574572
const nextFast = fastMode || undefined;
575-
if (currentModelSelection.provider === "opencode") {
573+
if (selectedProviderDriver === "opencode") {
576574
return;
577575
}
578-
const updated: ModelSelection = {
579-
...currentModelSelection,
580-
options: { ...currentModelSelection.options, fastMode: nextFast },
581-
};
576+
const updated = withModelSelectionOption(currentModelSelection, "fastMode", nextFast);
582577
void props.onUpdateModelSelection(updated);
583578
return;
584579
}
585580
if (event.startsWith("options:context-window:")) {
586581
const contextWindow = event.slice("options:context-window:".length);
587582
const updated: ModelSelection =
588-
currentModelSelection.provider === "claudeAgent"
589-
? {
590-
...currentModelSelection,
591-
options: { ...currentModelSelection.options, contextWindow },
592-
}
583+
selectedProviderDriver === "claudeAgent"
584+
? withModelSelectionOption(currentModelSelection, "contextWindow", contextWindow)
593585
: currentModelSelection;
594586
void props.onUpdateModelSelection(updated);
595587
return;

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ export const ThreadDetailScreen = memo(function ThreadDetailScreen(props: Thread
201201
const { onOpenDrawer, onRefresh } = props;
202202

203203
const insets = useSafeAreaInsets();
204-
const agentLabel = `${props.selectedThread.modelSelection.provider} agent`;
204+
const agentLabel = `${props.selectedThread.modelSelection.instanceId} agent`;
205205
const composerBottomInset = Math.max(insets.bottom, 12);
206206
const [composerExpanded, setComposerExpanded] = useState(false);
207207
const composerChrome = composerExpanded ? COMPOSER_EXPANDED_CHROME : COMPOSER_COLLAPSED_CHROME;

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

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -84,23 +84,23 @@ export function ThreadGitControls(props: {
8484
}>();
8585
const { gitStatus, gitOperationLabel, onPull, onRunAction } = props;
8686

87-
const currentBranchLabel = gitStatus?.branch ?? props.currentBranch ?? "Detached HEAD";
87+
const currentBranchLabel = gitStatus?.refName ?? props.currentBranch ?? "Detached HEAD";
8888
const busy = gitOperationLabel !== null;
8989
const isRepo = gitStatus?.isRepo ?? true;
90-
const hasOriginRemote = gitStatus?.hasOriginRemote ?? false;
91-
const isDefaultBranch = gitStatus?.isDefaultBranch ?? false;
90+
const hasPrimaryRemote = gitStatus?.hasPrimaryRemote ?? false;
91+
const isDefaultRef = gitStatus?.isDefaultRef ?? false;
9292

9393
const quickAction = useMemo(
9494
() =>
9595
isRepo
96-
? resolveQuickAction(gitStatus, busy, isDefaultBranch, hasOriginRemote)
96+
? resolveQuickAction(gitStatus, busy, isDefaultRef, hasPrimaryRemote)
9797
: {
9898
label: "Git unavailable",
9999
disabled: true,
100100
kind: "show_hint" as const,
101101
hint: "This workspace is not a git repository.",
102102
},
103-
[busy, gitStatus, hasOriginRemote, isDefaultBranch, isRepo],
103+
[busy, gitStatus, hasPrimaryRemote, isDefaultRef, isRepo],
104104
);
105105

106106
const quickActionHint = quickAction.disabled
@@ -143,12 +143,12 @@ export function ThreadGitControls(props: {
143143
input.action === "commit_push_pr"
144144
? input.action
145145
: null;
146-
const branchName = gitStatus?.branch;
146+
const branchName = gitStatus?.refName;
147147
if (
148148
branchName &&
149149
confirmableAction &&
150150
!input.featureBranch &&
151-
requiresDefaultBranchConfirmation(input.action, isDefaultBranch)
151+
requiresDefaultBranchConfirmation(input.action, isDefaultRef)
152152
) {
153153
router.push({
154154
pathname: "/threads/[environmentId]/[threadId]/git-confirm",
@@ -167,7 +167,7 @@ export function ThreadGitControls(props: {
167167

168168
await onRunAction(input);
169169
},
170-
[environmentId, gitStatus, isDefaultBranch, onRunAction, router, threadId],
170+
[environmentId, gitStatus, isDefaultRef, onRunAction, router, threadId],
171171
);
172172

173173
const runQuickAction = useCallback(async () => {
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import type { ClaudeAgentEffort } from "@t3tools/contracts";
2-
31
export const CLAUDE_AGENT_EFFORT_OPTIONS = [
42
"low",
53
"medium",
64
"high",
75
"xhigh",
86
"max",
97
"ultrathink",
10-
] as const satisfies readonly ClaudeAgentEffort[];
8+
] as const;
9+
10+
export type ClaudeAgentEffort = (typeof CLAUDE_AGENT_EFFORT_OPTIONS)[number];

apps/mobile/src/features/threads/git/GitBranchesSheet.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export function GitBranchesSheet() {
3232
cwd: selectedThreadCwd,
3333
});
3434

35-
const currentBranchLabel = gitStatus.data?.branch ?? selectedThread?.branch ?? "Detached HEAD";
35+
const currentBranchLabel = gitStatus.data?.refName ?? selectedThread?.branch ?? "Detached HEAD";
3636
const currentWorktreePath = selectedThreadWorktreePath;
3737
const availableBranches = gitState.selectedThreadBranches;
3838
const branchesLoading = gitState.selectedThreadBranchesLoading;

apps/mobile/src/features/threads/git/GitCommitSheet.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export function GitCommitSheet() {
3333
});
3434

3535
const busy = gitState.gitOperationLabel !== null;
36-
const isDefaultBranch = gitStatus.data?.isDefaultBranch ?? false;
36+
const isDefaultRef = gitStatus.data?.isDefaultRef ?? false;
3737
const allFiles = gitStatus.data?.workingTree?.files ?? [];
3838

3939
const [dialogCommitMessage, setDialogCommitMessage] = useState("");
@@ -76,10 +76,10 @@ export function GitCommitSheet() {
7676
<View className="flex-row items-center justify-between gap-3">
7777
<Text className="text-foreground-muted text-[13px] font-medium">Branch</Text>
7878
<Text className="text-foreground text-[15px] font-t3-bold">
79-
{gitStatus.data?.branch ?? "(detached HEAD)"}
79+
{gitStatus.data?.refName ?? "(detached HEAD)"}
8080
</Text>
8181
</View>
82-
{isDefaultBranch ? (
82+
{isDefaultRef ? (
8383
<Text
8484
className="text-[12px] leading-[18px]"
8585
style={{ color: isDarkMode ? "#fbbf24" : "#b45309" }}

0 commit comments

Comments
 (0)