From 16f6f27027687fe45ffab0f86aaab291c0622121 Mon Sep 17 00:00:00 2001 From: Iuliia Ivashko Date: Fri, 4 Sep 2026 18:39:05 +0300 Subject: [PATCH] fix(ui): stop the echo of a Default send from erasing the recorded Default The send write was fixed, but the message that send echoes back arrives with model metadata and no effort, and its model matches the one the send just saved. That is exactly when `shouldPreserveManualModelOverride` declines to protect the selection, so the history branch ran with no variant and recorded "no choice" over the user's explicit `Default`. Picking Default, sending, then switching agent and back still put the settings default in the picker. A message carrying no effort is not evidence that the session has none. The history branch now keeps whatever the session already recorded when the message carries no variant, and a concrete historical effort still replaces it. The behavior test ran the real guard through a mock that returned a fixed answer, so the failing branch had no coverage. It now calls the real function unless a test opts out, and the new case fails without this change. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01EZuVVgziiLjD81W5vaxdH2 --- .../ui/src/components/chat/ModelControls.tsx | 18 +++++++- .../ModelControls.variant.behavior.test.tsx | 41 +++++++++++++++++-- 2 files changed, 53 insertions(+), 6 deletions(-) diff --git a/packages/ui/src/components/chat/ModelControls.tsx b/packages/ui/src/components/chat/ModelControls.tsx index c934bfef..697d0e7f 100644 --- a/packages/ui/src/components/chat/ModelControls.tsx +++ b/packages/ui/src/components/chat/ModelControls.tsx @@ -878,11 +878,24 @@ export const ModelControls: React.FC = ({ && getModelVariantOptions(latestLoadedUserChoice.providerID, latestLoadedUserChoice.modelID).includes(latestLoadedUserChoice.variant) ? latestLoadedUserChoice.variant : undefined; + const restoreAgentName = latestLoadedUserChoice.agent || currentAgentName || undefined; + // A message carrying no effort is not evidence that the user has none: + // a send under an explicit "Default" carries none either, and the echo + // of that very send arrives here. Keep what the session already + // recorded, and let a concrete historical effort replace it. + const restoredVariant = historicalVariant ?? (currentSessionId && restoreAgentName + ? getAgentModelVariantForSession( + currentSessionId, + restoreAgentName, + latestLoadedUserChoice.providerID, + latestLoadedUserChoice.modelID, + ) + : undefined); const applyResult = applyModelSelectionWithVariant( latestLoadedUserChoice.providerID, latestLoadedUserChoice.modelID, - historicalVariant, - latestLoadedUserChoice.agent || currentAgentName || undefined, + restoredVariant, + restoreAgentName, ); if (applyResult !== 'applied') { return; @@ -906,6 +919,7 @@ export const ModelControls: React.FC = ({ latestLoadedUserChoice, setAgent, applyModelSelectionWithVariant, + getAgentModelVariantForSession, getModelVariantOptions, getSessionModelSelection, resolveModelVariantSelection, diff --git a/packages/ui/src/components/chat/ModelControls.variant.behavior.test.tsx b/packages/ui/src/components/chat/ModelControls.variant.behavior.test.tsx index 777a6cc8..a9fec6dd 100644 --- a/packages/ui/src/components/chat/ModelControls.variant.behavior.test.tsx +++ b/packages/ui/src/components/chat/ModelControls.variant.behavior.test.tsx @@ -40,7 +40,7 @@ const provider = { id: PROVIDER_ID, name: PROVIDER_ID, models: [model] }; const agent = { name: AGENT, mode: 'primary' as const }; let latestUserChoice: UserModelChoice | null = null; -let preserveManualOverride = false; +let forcePreserveManualOverride: boolean | null = null; /** Every effort written for the session, in order, including `undefined`. */ const variantWrites: VariantChoice[] = []; @@ -175,9 +175,19 @@ const useUIStore = create(() => ({ const passthrough = ({ children }: React.PropsWithChildren) =>
{children}
; +// Captured by value before the module is replaced: reading it back off the +// namespace afterwards would resolve to the replacement and recurse. +const { shouldPreserveManualModelOverride: realShouldPreserveManualModelOverride } = + await import('@/lib/messages/userModelChoice'); + mock.module('@/lib/messages/userModelChoice', () => ({ findLatestUserModelChoice: () => latestUserChoice, - shouldPreserveManualModelOverride: () => preserveManualOverride, + // The real guard, unless a test opts out: whether it fires decides which + // restore branch runs, and the branch that erased a recorded Default is the + // one it declines to protect. + shouldPreserveManualModelOverride: (args: Parameters[0]) => ( + forcePreserveManualOverride ?? realShouldPreserveManualModelOverride(args) + ), })); mock.module('@/stores/useConfigStore', () => ({ useConfigStore })); @@ -316,7 +326,7 @@ describe('ModelControls effort restore', () => { variantWrites.length = 0; overrideWrites.length = 0; latestUserChoice = null; - preserveManualOverride = false; + forcePreserveManualOverride = null; useSelectionStore.setState({ savedVariant: undefined }); useConfigStore.setState({ currentProviderId: PROVIDER_ID, @@ -356,9 +366,32 @@ describe('ModelControls effort restore', () => { } }); + test('the echo of a Default send does not erase the recorded Default', async () => { + // The reported repro. The send under "Default" carried no effort, so the + // message it echoes back carries none either, and its model matches the one + // the send saved — which is exactly when the manual-override guard declines + // to protect the selection and the history branch runs. + latestUserChoice = { id: 'msg-echo', agent: AGENT, providerID: PROVIDER_ID, modelID: MODEL_ID }; + useSelectionStore.setState({ savedVariant: null }); + useConfigStore.setState({ + selectionSource: 'manual', + settingsDefaultVariant: 'low', + currentVariantSelection: { override: null, inherited: 'low' }, + }); + + const { cleanup } = await renderModelControls(); + try { + expect(useSelectionStore.getState().savedVariant).toBeNull(); + expect(useConfigStore.getState().currentVariantSelection.override).toBeNull(); + expect(useConfigStore.getState().currentVariant).toBe(undefined); + } finally { + await cleanup(); + } + }); + test('a preserved manual override keeps a recorded explicit Default', async () => { latestUserChoice = { id: 'msg-3', agent: AGENT, providerID: PROVIDER_ID, modelID: MODEL_ID, variant: 'high' }; - preserveManualOverride = true; + forcePreserveManualOverride = true; useSelectionStore.setState({ savedVariant: null }); useConfigStore.setState({ selectionSource: 'manual' });