From ca4021a5cb0bdd24b71431007abcb785716a37f6 Mon Sep 17 00:00:00 2001 From: F4llen <76191805+f4llenz@users.noreply.github.com> Date: Thu, 12 Nov 2026 21:07:49 +0000 Subject: [PATCH 1/2] fix(web): re-picking the current model in Settings keeps its traits Picking the model a settings picker already shows saved it without options, so its reasoning effort fell back to the default. The default, fallback, text generation, source control writer, review guide, voice cleanup and text generator pickers now keep the shown selection on a re-pick. --- .../settings/ProjectDefaultsSettings.tsx | 12 +++++++--- .../components/settings/SettingsPanels.tsx | 8 +++++-- .../settings/SourceControlWritingSettings.tsx | 14 +++++++++--- .../settings/TextGeneratorsSettings.tsx | 4 ++-- .../settings/VoiceSettingsPanel.tsx | 4 ++-- packages/shared/src/model.test.ts | 22 +++++++++++++++++++ packages/shared/src/model.ts | 11 ++++++++++ 7 files changed, 63 insertions(+), 12 deletions(-) diff --git a/apps/web/src/components/settings/ProjectDefaultsSettings.tsx b/apps/web/src/components/settings/ProjectDefaultsSettings.tsx index 182b8bc5b1fdf..930a024242cae 100644 --- a/apps/web/src/components/settings/ProjectDefaultsSettings.tsx +++ b/apps/web/src/components/settings/ProjectDefaultsSettings.tsx @@ -4,7 +4,7 @@ import { type WorktreeSubmodules, managedRuntimeModeDenial, } from "@t3tools/contracts"; -import { createModelSelection } from "@t3tools/shared/model"; +import { createModelSelection, pickModelSelection } from "@t3tools/shared/model"; import { resolveProjectSettings } from "@t3tools/shared/projectSettings"; import { useNavigate } from "@tanstack/react-router"; @@ -172,7 +172,7 @@ export function ProjectDefaultsSettings({ category }: { category: ProjectSetting }); }} onInstanceModelChange={(instanceId, model) => - setModel(createModelSelection(instanceId, model)) + setModel(pickModelSelection(selection, instanceId, model)) } /> {!mixedModel && !presentation.providerNote && !effortDisabledReason && activeEntry ? ( @@ -288,7 +288,13 @@ export function ProjectDefaultsSettings({ category }: { category: ProjectSetting }); return; } - updateSettings({ fallbackModelSelection: createModelSelection(instanceId, model) }); + updateSettings({ + fallbackModelSelection: pickModelSelection( + settings.fallbackModelSelection, + instanceId, + model, + ), + }); }} /> {settings.fallbackModelSelection !== null && fallbackEntry && !mixedFallback ? ( diff --git a/apps/web/src/components/settings/SettingsPanels.tsx b/apps/web/src/components/settings/SettingsPanels.tsx index 31234fb6be51a..cea5999e32c59 100644 --- a/apps/web/src/components/settings/SettingsPanels.tsx +++ b/apps/web/src/components/settings/SettingsPanels.tsx @@ -27,7 +27,7 @@ import { ToolCallVisibility, } from "@t3tools/contracts/settings"; import { desktopUpdateChannelOf } from "@t3tools/shared/cliRelease"; -import { createModelSelection } from "@t3tools/shared/model"; +import { createModelSelection, pickModelSelection } from "@t3tools/shared/model"; import { resolvePastedPath } from "@t3tools/shared/path"; import * as Equal from "effect/Equal"; import * as Schema from "effect/Schema"; @@ -2031,7 +2031,11 @@ export function ThreadsSettingsPanel() { textGenerationModelSelection: resolveAppModelSelectionState( { ...settings, - textGenerationModelSelection: createModelSelection(instanceId, model), + textGenerationModelSelection: pickModelSelection( + textGenerationModelSelection, + instanceId, + model, + ), }, textGenerationProviders, ), diff --git a/apps/web/src/components/settings/SourceControlWritingSettings.tsx b/apps/web/src/components/settings/SourceControlWritingSettings.tsx index 26cf6eb3dcbb2..52323e2beb6b3 100644 --- a/apps/web/src/components/settings/SourceControlWritingSettings.tsx +++ b/apps/web/src/components/settings/SourceControlWritingSettings.tsx @@ -7,7 +7,7 @@ import { textGeneratorForSelection, } from "@t3tools/contracts"; import { DEFAULT_UNIFIED_SETTINGS } from "@t3tools/contracts/settings"; -import { createModelSelection } from "@t3tools/shared/model"; +import { createModelSelection, pickModelSelection } from "@t3tools/shared/model"; import { resolveReviewGuideModelSelection, resolveSourceControlWriterModelSelection, @@ -365,7 +365,11 @@ export function SourceControlWritingSettingsSection() { return; } updateSettings({ - reviewGuideModelSelection: createModelSelection(instanceId, model), + reviewGuideModelSelection: pickModelSelection( + reviewGuideSelection, + instanceId, + model, + ), }); }} /> @@ -564,7 +568,11 @@ export function SourceControlWritingSettingsSection() { return; } updateSettings({ - sourceControlWriterModelSelection: createModelSelection(instanceId, model), + sourceControlWriterModelSelection: pickModelSelection( + activeSelection, + instanceId, + model, + ), }); }} /> diff --git a/apps/web/src/components/settings/TextGeneratorsSettings.tsx b/apps/web/src/components/settings/TextGeneratorsSettings.tsx index ce0e729309bf2..76bf4f6b41e58 100644 --- a/apps/web/src/components/settings/TextGeneratorsSettings.tsx +++ b/apps/web/src/components/settings/TextGeneratorsSettings.tsx @@ -7,7 +7,7 @@ import { TextGeneratorConfig, type TextGeneratorModel, } from "@t3tools/contracts"; -import { createModelSelection } from "@t3tools/shared/model"; +import { pickModelSelection } from "@t3tools/shared/model"; import { AsyncResult } from "effect/reactivity"; import * as Option from "effect/Option"; import * as Schema from "effect/Schema"; @@ -571,7 +571,7 @@ function TextGenerationTaskPicker({ )} triggerClassName={SETTINGS_PICKER_TRIGGER_CLASSNAME} onInstanceModelChange={(instanceId, model) => - updateSettings({ [settingKey]: createModelSelection(instanceId, model) }) + updateSettings({ [settingKey]: pickModelSelection(selection, instanceId, model) }) } /> diff --git a/apps/web/src/components/settings/VoiceSettingsPanel.tsx b/apps/web/src/components/settings/VoiceSettingsPanel.tsx index 22ae59e55d31e..66cacf6eb7ee9 100644 --- a/apps/web/src/components/settings/VoiceSettingsPanel.tsx +++ b/apps/web/src/components/settings/VoiceSettingsPanel.tsx @@ -28,7 +28,7 @@ import { type SpeechModelUnloadTimeout, type VoiceShortcutMode, } from "@t3tools/contracts"; -import { createModelSelection } from "@t3tools/shared/model"; +import { createModelSelection, pickModelSelection } from "@t3tools/shared/model"; import { resolveSpeechPostProcessingModelSelection } from "@t3tools/shared/serverSettings"; import { cloudSpeechLanguages } from "@t3tools/shared/speech"; import { @@ -576,7 +576,7 @@ function CleanupModelRow() { triggerClassName={SETTINGS_PICKER_TRIGGER_CLASSNAME} getModelDisabledReason={modelDisabledReason} onInstanceModelChange={(instanceId, model) => - setModel(createModelSelection(instanceId, model)) + setModel(pickModelSelection(selection, instanceId, model)) } /> { ).toBe(false); expect(modelSelectionsEqual(left, { ...reordered, model: "gpt-5.5" })).toBe(false); }); + + it("keeps the options when the current model is picked again", () => { + const codex = ProviderInstanceId.make("codex"); + const current = createModelSelection(codex, "gpt-5.4", [ + { id: "reasoningEffort", value: "high" }, + ]); + + expect(pickModelSelection(current, codex, "gpt-5.4")).toBe(current); + expect(pickModelSelection(current, codex, "gpt-5.5")).toEqual({ + instanceId: "codex", + model: "gpt-5.5", + }); + expect(pickModelSelection(current, ProviderInstanceId.make("codex_work"), "gpt-5.4")).toEqual({ + instanceId: "codex_work", + model: "gpt-5.4", + }); + expect(pickModelSelection(null, codex, "gpt-5.4")).toEqual({ + instanceId: "codex", + model: "gpt-5.4", + }); + }); }); describe("applyClaudePromptEffortPrefix", () => { diff --git a/packages/shared/src/model.ts b/packages/shared/src/model.ts index 6714bae9b8de1..babe1347cc056 100644 --- a/packages/shared/src/model.ts +++ b/packages/shared/src/model.ts @@ -538,6 +538,17 @@ export function createModelSelection( return selections.length > 0 ? { ...base, options: selections } : base; } +/** A model picker's pick: re-picking the current model keeps its options, another model starts without any. */ +export function pickModelSelection( + current: ModelSelection | null | undefined, + instanceId: ProviderInstanceId, + model: string, +): ModelSelection { + return current?.instanceId === instanceId && current.model === model + ? current + : createModelSelection(instanceId, model); +} + export function resolvePromptInjectedEffort( caps: ModelCapabilities, rawEffort: string | null | undefined, From 043c8399476c55e6b212fa36281499d72ceae61d Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Sat, 14 Nov 2026 07:58:36 +0000 Subject: [PATCH 2/2] fix(web): a model pick over a mixed selection starts without traits With several projects selected and their model settings differing, the picker shows "Mixed" but its selection is the representative project's. Re-picking that model must not copy the representative's traits onto every project. Co-Authored-By: Claude Opus 5.5 --- apps/web/src/components/settings/ProjectDefaultsSettings.tsx | 4 ++-- .../src/components/settings/SourceControlWritingSettings.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/web/src/components/settings/ProjectDefaultsSettings.tsx b/apps/web/src/components/settings/ProjectDefaultsSettings.tsx index 930a024242cae..97c9ca0483ab8 100644 --- a/apps/web/src/components/settings/ProjectDefaultsSettings.tsx +++ b/apps/web/src/components/settings/ProjectDefaultsSettings.tsx @@ -172,7 +172,7 @@ export function ProjectDefaultsSettings({ category }: { category: ProjectSetting }); }} onInstanceModelChange={(instanceId, model) => - setModel(pickModelSelection(selection, instanceId, model)) + setModel(pickModelSelection(mixedModel ? null : selection, instanceId, model)) } /> {!mixedModel && !presentation.providerNote && !effortDisabledReason && activeEntry ? ( @@ -290,7 +290,7 @@ export function ProjectDefaultsSettings({ category }: { category: ProjectSetting } updateSettings({ fallbackModelSelection: pickModelSelection( - settings.fallbackModelSelection, + mixedFallback ? null : settings.fallbackModelSelection, instanceId, model, ), diff --git a/apps/web/src/components/settings/SourceControlWritingSettings.tsx b/apps/web/src/components/settings/SourceControlWritingSettings.tsx index 52323e2beb6b3..6270639f4c167 100644 --- a/apps/web/src/components/settings/SourceControlWritingSettings.tsx +++ b/apps/web/src/components/settings/SourceControlWritingSettings.tsx @@ -366,7 +366,7 @@ export function SourceControlWritingSettingsSection() { } updateSettings({ reviewGuideModelSelection: pickModelSelection( - reviewGuideSelection, + mixedReviewGuideModel ? null : reviewGuideSelection, instanceId, model, ), @@ -569,7 +569,7 @@ export function SourceControlWritingSettingsSection() { } updateSettings({ sourceControlWriterModelSelection: pickModelSelection( - activeSelection, + mixedWriterModel ? null : activeSelection, instanceId, model, ),