Skip to content

Commit 8bd315f

Browse files
committed
refactor: move model picker to Settings and fix runtime validation
Move the commit-message model dropdown from the git diff panel to Settings > Git. Source the model list from the existing codex-section fetch to avoid duplicate getModelList fan-out. Replace the old normalize-and-persist hook with a pure runtime guard (effectiveCommitMessageModelId) that validates the saved preference against the active workspace's models without overwriting the persisted setting — so switching workspaces no longer silently clears the choice.
1 parent 5fe8a93 commit 8bd315f

15 files changed

Lines changed: 79 additions & 291 deletions

File tree

src-tauri/src/bin/codex_monitor_daemon.rs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1216,14 +1216,10 @@ impl DaemonState {
12161216
)
12171217
.await?;
12181218
let diff = git_ui_core::collect_workspace_diff_core(&repo_root)?;
1219-
let (commit_message_prompt, default_commit_message_model_id) = {
1219+
let commit_message_prompt = {
12201220
let settings = self.app_settings.lock().await;
1221-
(
1222-
settings.commit_message_prompt.clone(),
1223-
settings.commit_message_model_id.clone(),
1224-
)
1221+
settings.commit_message_prompt.clone()
12251222
};
1226-
let commit_message_model_id = commit_message_model_id.or(default_commit_message_model_id);
12271223
codex_aux_core::generate_commit_message_core(
12281224
&self.sessions,
12291225
workspace_id,

src-tauri/src/codex/mod.rs

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -880,12 +880,6 @@ pub(crate) async fn generate_commit_message(
880880
app: AppHandle,
881881
) -> Result<String, String> {
882882
if remote_backend::is_remote_mode(&*state).await {
883-
let commit_message_model_id = if commit_message_model_id.is_some() {
884-
commit_message_model_id
885-
} else {
886-
let settings = state.app_settings.lock().await;
887-
settings.commit_message_model_id.clone()
888-
};
889883
let value = remote_backend::call_remote(
890884
&*state,
891885
app,
@@ -901,14 +895,10 @@ pub(crate) async fn generate_commit_message(
901895

902896
let diff = crate::git::get_workspace_diff(&workspace_id, &state).await?;
903897

904-
let (commit_message_prompt, default_commit_message_model_id) = {
898+
let commit_message_prompt = {
905899
let settings = state.app_settings.lock().await;
906-
(
907-
settings.commit_message_prompt.clone(),
908-
settings.commit_message_model_id.clone(),
909-
)
900+
settings.commit_message_prompt.clone()
910901
};
911-
let commit_message_model_id = commit_message_model_id.or(default_commit_message_model_id);
912902
crate::shared::codex_aux_core::generate_commit_message_core(
913903
&state.sessions,
914904
workspace_id,

src-tauri/src/shared/codex_aux_core.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -616,6 +616,7 @@ where
616616
sessions,
617617
workspace_id,
618618
prompt,
619+
None,
619620
on_hide_thread,
620621
"Timeout waiting for agent configuration generation",
621622
"Unknown error during agent configuration generation",

src/App.tsx

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ import { useWorkspaceLaunchScript } from "@app/hooks/useWorkspaceLaunchScript";
107107
import { useWorkspaceLaunchScripts } from "@app/hooks/useWorkspaceLaunchScripts";
108108
import { useWorktreeSetupScript } from "@app/hooks/useWorktreeSetupScript";
109109
import { useGitCommitController } from "@app/hooks/useGitCommitController";
110-
import { useCommitMessageModelSelection } from "@app/hooks/useCommitMessageModelSelection";
110+
import { effectiveCommitMessageModelId } from "@/features/git/utils/commitMessageModelSelection";
111111
import { WorkspaceHome } from "@/features/workspaces/components/WorkspaceHome";
112112
import { MobileServerSetupWizard } from "@/features/mobile/components/MobileServerSetupWizard";
113113
import { useMobileServerSetup } from "@/features/mobile/hooks/useMobileServerSetup";
@@ -429,15 +429,10 @@ function MainApp() {
429429
setAccessMode,
430430
persistThreadCodexParams,
431431
});
432-
const {
433-
resolvedCommitMessageModelId,
434-
onCommitMessageModelChange,
435-
} = useCommitMessageModelSelection({
436-
models,
437-
commitMessageModelId: appSettings.commitMessageModelId,
438-
setAppSettings,
439-
queueSaveSettings,
440-
});
432+
const commitMessageModelId = useMemo(
433+
() => effectiveCommitMessageModelId(models, appSettings.commitMessageModelId),
434+
[models, appSettings.commitMessageModelId],
435+
);
441436

442437
const composerShortcuts = {
443438
modelShortcut: appSettings.composerModelShortcut,
@@ -1458,7 +1453,7 @@ function MainApp() {
14581453
activeWorkspace,
14591454
activeWorkspaceId,
14601455
activeWorkspaceIdRef,
1461-
commitMessageModelId: resolvedCommitMessageModelId,
1456+
commitMessageModelId,
14621457
gitStatus,
14631458
refreshGitStatus,
14641459
refreshGitLog,
@@ -2197,8 +2192,6 @@ function MainApp() {
21972192
commitMessageError,
21982193
onCommitMessageChange: handleCommitMessageChange,
21992194
onGenerateCommitMessage: handleGenerateCommitMessage,
2200-
commitMessageModelId: resolvedCommitMessageModelId,
2201-
onCommitMessageModelChange,
22022195
onCommit: handleCommit,
22032196
onCommitAndPush: handleCommitAndPush,
22042197
onCommitAndSync: handleCommitAndSync,

src/features/app/hooks/useCommitMessageModelSelection.ts

Lines changed: 0 additions & 54 deletions
This file was deleted.

src/features/git/components/GitDiffPanel.tsx

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { GitHubIssue, GitHubPullRequest, GitLogEntry, ModelOption } from "../../../types";
1+
import type { GitHubIssue, GitHubPullRequest, GitLogEntry } from "../../../types";
22
import type { MouseEvent as ReactMouseEvent } from "react";
33
import { Menu, MenuItem } from "@tauri-apps/api/menu";
44
import { LogicalPosition } from "@tauri-apps/api/dpi";
@@ -127,9 +127,6 @@ type GitDiffPanelProps = {
127127
commitMessageError?: string | null;
128128
onCommitMessageChange?: (value: string) => void;
129129
onGenerateCommitMessage?: () => void | Promise<void>;
130-
models?: ModelOption[];
131-
commitMessageModelId?: string | null;
132-
onCommitMessageModelChange?: (id: string | null) => void;
133130
// Git operations
134131
onCommit?: () => void | Promise<void>;
135132
onCommitAndPush?: () => void | Promise<void>;
@@ -219,9 +216,6 @@ export function GitDiffPanel({
219216
commitMessageError = null,
220217
onCommitMessageChange,
221218
onGenerateCommitMessage,
222-
models = [],
223-
commitMessageModelId = null,
224-
onCommitMessageModelChange,
225219
onCommit,
226220
onCommitAndPush: _onCommitAndPush,
227221
onCommitAndSync: _onCommitAndSync,
@@ -736,9 +730,6 @@ export function GitDiffPanel({
736730
commitMessageLoading={commitMessageLoading}
737731
canGenerateCommitMessage={canGenerateCommitMessage}
738732
onGenerateCommitMessage={onGenerateCommitMessage}
739-
models={models}
740-
commitMessageModelId={commitMessageModelId}
741-
onCommitMessageModelChange={onCommitMessageModelChange}
742733
stagedFiles={stagedFiles}
743734
unstagedFiles={unstagedFiles}
744735
commitLoading={commitLoading}

src/features/git/components/GitDiffPanelModeContent.tsx

Lines changed: 2 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
import type { GitHubIssue, GitHubPullRequest, GitLogEntry, ModelOption } from "../../../types";
1+
import type { GitHubIssue, GitHubPullRequest, GitLogEntry } from "../../../types";
22
import type { MouseEvent as ReactMouseEvent } from "react";
3-
import { useCallback, useEffect, useRef, useState } from "react";
3+
import { useCallback, useEffect, useState } from "react";
44
import { openUrl } from "@tauri-apps/plugin-opener";
55
import ArrowLeftRight from "lucide-react/dist/esm/icons/arrow-left-right";
66
import ChevronDown from "lucide-react/dist/esm/icons/chevron-down";
77
import ChevronRight from "lucide-react/dist/esm/icons/chevron-right";
8-
import Cpu from "lucide-react/dist/esm/icons/cpu";
98
import Download from "lucide-react/dist/esm/icons/download";
109
import RotateCcw from "lucide-react/dist/esm/icons/rotate-ccw";
1110
import RotateCw from "lucide-react/dist/esm/icons/rotate-cw";
@@ -15,11 +14,6 @@ import {
1514
MagicSparkleIcon,
1615
MagicSparkleLoaderIcon,
1716
} from "@/features/shared/components/MagicSparkleIcon";
18-
import {
19-
PopoverMenuItem,
20-
PopoverSurface,
21-
} from "../../design-system/components/popover/PopoverPrimitives";
22-
import { useDismissibleMenu } from "../../app/hooks/useDismissibleMenu";
2317
import type { GitPanelMode } from "../types";
2418
import type { PerFileDiffGroup } from "../utils/perFileThreadDiffs";
2519
import {
@@ -330,9 +324,6 @@ type GitDiffModeContentProps = {
330324
commitMessageLoading: boolean;
331325
canGenerateCommitMessage: boolean;
332326
onGenerateCommitMessage?: () => void | Promise<void>;
333-
models: ModelOption[];
334-
commitMessageModelId: string | null;
335-
onCommitMessageModelChange?: (id: string | null) => void;
336327
stagedFiles: DiffFile[];
337328
unstagedFiles: DiffFile[];
338329
commitLoading: boolean;
@@ -389,9 +380,6 @@ export function GitDiffModeContent({
389380
commitMessageLoading,
390381
canGenerateCommitMessage,
391382
onGenerateCommitMessage,
392-
models,
393-
commitMessageModelId,
394-
onCommitMessageModelChange,
395383
stagedFiles,
396384
unstagedFiles,
397385
commitLoading,
@@ -416,20 +404,6 @@ export function GitDiffModeContent({
416404
onShowFileMenu,
417405
onDiffListClick,
418406
}: GitDiffModeContentProps) {
419-
const [commitModelOpen, setCommitModelOpen] = useState(false);
420-
const commitModelRef = useRef<HTMLDivElement | null>(null);
421-
useDismissibleMenu({
422-
isOpen: commitModelOpen,
423-
containerRef: commitModelRef,
424-
onClose: () => setCommitModelOpen(false),
425-
});
426-
const selectedCommitModel = commitMessageModelId
427-
? models.find((m) => m.model === commitMessageModelId) ?? null
428-
: models.find((m) => m.isDefault) ?? models[0] ?? null;
429-
const commitModelLabel = selectedCommitModel?.displayName?.trim()
430-
|| selectedCommitModel?.model?.trim()
431-
|| "Model";
432-
433407
const normalizedGitRoot = normalizeRootPath(gitRoot);
434408
const missingRepo = isMissingRepo(error);
435409
const gitRootNotFound = isGitRootNotFound(error);
@@ -572,61 +546,6 @@ export function GitDiffModeContent({
572546
)}
573547
</button>
574548
</div>
575-
{models.length > 0 && (
576-
<div className="open-app-menu commit-model-menu" ref={commitModelRef}>
577-
<div className="open-app-button">
578-
<button
579-
type="button"
580-
className="ghost open-app-action"
581-
onClick={() => setCommitModelOpen((prev) => !prev)}
582-
disabled={commitMessageLoading}
583-
aria-label="Select model for commit message"
584-
>
585-
<span className="open-app-label">
586-
<Cpu size={14} aria-hidden />
587-
{commitModelLabel}
588-
</span>
589-
</button>
590-
<button
591-
type="button"
592-
className="ghost open-app-toggle"
593-
onClick={() => setCommitModelOpen((prev) => !prev)}
594-
disabled={commitMessageLoading}
595-
aria-haspopup="menu"
596-
aria-expanded={commitModelOpen}
597-
aria-label="Toggle model menu"
598-
>
599-
<ChevronDown size={14} aria-hidden />
600-
</button>
601-
</div>
602-
{commitModelOpen && (
603-
<PopoverSurface
604-
className="open-app-dropdown commit-model-dropdown"
605-
role="menu"
606-
>
607-
{models.map((m) => {
608-
const isActive = commitMessageModelId
609-
? m.model === commitMessageModelId
610-
: m === selectedCommitModel;
611-
return (
612-
<PopoverMenuItem
613-
key={m.id}
614-
className="open-app-option"
615-
onClick={() => {
616-
onCommitMessageModelChange?.(m.model);
617-
setCommitModelOpen(false);
618-
}}
619-
icon={<Cpu size={14} aria-hidden />}
620-
active={isActive}
621-
>
622-
{m.displayName?.trim() || m.model}
623-
</PopoverMenuItem>
624-
);
625-
})}
626-
</PopoverSurface>
627-
)}
628-
</div>
629-
)}
630549
<CommitButton
631550
commitMessage={commitMessage}
632551
hasStagedFiles={stagedFiles.length > 0}
Lines changed: 10 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { describe, expect, it } from "vitest";
22
import type { ModelOption } from "@/types";
3-
import { resolveCommitMessageModelSelection } from "./commitMessageModelSelection";
3+
import { effectiveCommitMessageModelId } from "./commitMessageModelSelection";
44

55
const MODELS: ModelOption[] = [
66
{
@@ -23,45 +23,20 @@ const MODELS: ModelOption[] = [
2323
},
2424
];
2525

26-
describe("resolveCommitMessageModelSelection", () => {
27-
it("keeps null selection unchanged", () => {
28-
expect(resolveCommitMessageModelSelection(MODELS, null)).toEqual({
29-
resolvedModelId: null,
30-
normalizedModelId: null,
31-
shouldNormalize: false,
32-
});
26+
describe("effectiveCommitMessageModelId", () => {
27+
it("passes through null when no model is saved", () => {
28+
expect(effectiveCommitMessageModelId(MODELS, null)).toBeNull();
3329
});
3430

35-
it("keeps explicit selection when it still exists", () => {
36-
expect(resolveCommitMessageModelSelection(MODELS, "gpt-5.1")).toEqual({
37-
resolvedModelId: "gpt-5.1",
38-
normalizedModelId: "gpt-5.1",
39-
shouldNormalize: false,
40-
});
31+
it("returns the saved model when it exists in the workspace", () => {
32+
expect(effectiveCommitMessageModelId(MODELS, "gpt-5.1")).toBe("gpt-5.1");
4133
});
4234

43-
it("falls back to the default model when selected model disappears", () => {
44-
expect(resolveCommitMessageModelSelection(MODELS, "gpt-4.1")).toEqual({
45-
resolvedModelId: "gpt-5.2",
46-
normalizedModelId: "gpt-5.2",
47-
shouldNormalize: true,
48-
});
35+
it("falls back to null when saved model is unavailable in the workspace", () => {
36+
expect(effectiveCommitMessageModelId(MODELS, "gpt-4.1")).toBeNull();
4937
});
5038

51-
it("falls back to first model when no default exists", () => {
52-
const noDefault = MODELS.map((model) => ({ ...model, isDefault: false }));
53-
expect(resolveCommitMessageModelSelection(noDefault, "gpt-4.1")).toEqual({
54-
resolvedModelId: "gpt-5.1",
55-
normalizedModelId: "gpt-5.1",
56-
shouldNormalize: true,
57-
});
58-
});
59-
60-
it("normalizes to null when no models are available", () => {
61-
expect(resolveCommitMessageModelSelection([], "gpt-4.1")).toEqual({
62-
resolvedModelId: null,
63-
normalizedModelId: null,
64-
shouldNormalize: true,
65-
});
39+
it("falls back to null when no models are available", () => {
40+
expect(effectiveCommitMessageModelId([], "gpt-5.1")).toBeNull();
6641
});
6742
});

0 commit comments

Comments
 (0)