fix(ui): let restore paths report no effort without pinning Default

`commitVariantSelectionForModel` turned every `undefined` into an explicit
`Default` (`null`), but it serves two kinds of caller. The picker means "the
user chose Default"; the history and manual-override restores mean "nothing
was found". Restoring a session therefore recorded a choice nobody made, and
`resolveModelVariantSelection` collapsed that `null` back to `undefined`, so
the next restore recorded it again. Because an explicit `Default` outranks the
agent and settings defaults by design, the session latched onto `Default` and
the concrete effort its own history carried could not come back.

Move the decision to the callers: the four picker paths pass `variant ?? null`,
the restore paths pass their result through, and the resolver returns the
selection store's three states instead of two. The follow-up write in the
history restore goes with it — the apply above it already recorded the same
agent and model, and a second write could only disagree.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EZuVVgziiLjD81W5vaxdH2
This commit is contained in:
Iuliia Ivashko
2026-09-04 17:32:40 +03:00
co-authored by Claude Opus 5
parent cb3bc3bd1b
commit 025cd3d46c
2 changed files with 47 additions and 23 deletions
@@ -725,7 +725,14 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
return candidates.find((candidate) => candidate !== undefined && variantOptions.includes(candidate)); return candidates.find((candidate) => candidate !== undefined && variantOptions.includes(candidate));
}, [agents, currentAgentName, currentModelId, currentProviderId, currentSessionId, currentVariantSelection, effectiveCurrentVariant, getModelVariantOptions, settingsDefaultVariant, uiAgentName]); }, [agents, currentAgentName, currentModelId, currentProviderId, currentSessionId, currentVariantSelection, effectiveCurrentVariant, getModelVariantOptions, settingsDefaultVariant, uiAgentName]);
const resolveModelVariantSelection = React.useCallback((providerId: string, modelId: string) => { /**
* The session's recorded choice for this model, in the selection store's
* three states: an effort name, `null` for an explicit "Default", and
* `undefined` for no choice at all. Collapsing `null` into `undefined` here
* would hand a real "Default" back to the callers as "nothing chosen", and
* they would re-record it as a choice on the next write.
*/
const resolveModelVariantSelection = React.useCallback((providerId: string, modelId: string): string | null | undefined => {
const variantOptions = getModelVariantOptions(providerId, modelId); const variantOptions = getModelVariantOptions(providerId, modelId);
if (variantOptions.length === 0) { if (variantOptions.length === 0) {
return undefined; return undefined;
@@ -736,7 +743,7 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
const savedVariant = getAgentModelVariantForSession(currentSessionId, effectiveAgentName, providerId, modelId); const savedVariant = getAgentModelVariantForSession(currentSessionId, effectiveAgentName, providerId, modelId);
// An explicit "Default" is a choice: it stops the fallbacks below. // An explicit "Default" is a choice: it stops the fallbacks below.
if (savedVariant === null) { if (savedVariant === null) {
return undefined; return null;
} }
if (savedVariant && variantOptions.includes(savedVariant)) { if (savedVariant && variantOptions.includes(savedVariant)) {
return savedVariant; return savedVariant;
@@ -770,7 +777,16 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
return liveConfigAgentName || currentAgentName; return liveConfigAgentName || currentAgentName;
}, [currentAgentName, currentSessionId]); }, [currentAgentName, currentSessionId]);
const commitVariantSelectionForModel = React.useCallback((providerId: string, modelId: string, variant: string | undefined, agentNameOverride?: string | null) => { /**
* Records `variant` as this session's effort for the model, in the same
* three states the selection store defines: an effort name, `null` for an
* explicit "Default", `undefined` for no choice so the inherited effort
* applies. Callers decide which one they mean — the picker turns its own
* "Default" into `null`, while restore paths pass `undefined` through when
* they found nothing, because "the history carries no effort" is not the
* user having chosen "Default".
*/
const commitVariantSelectionForModel = React.useCallback((providerId: string, modelId: string, variant: string | null | undefined, agentNameOverride?: string | null) => {
const variantOptions = getModelVariantOptions(providerId, modelId); const variantOptions = getModelVariantOptions(providerId, modelId);
if (variantOptions.length === 0) { if (variantOptions.length === 0) {
manualVariantSelectionRef.current = false; manualVariantSelectionRef.current = false;
@@ -780,16 +796,14 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
manualVariantSelectionRef.current = true; manualVariantSelectionRef.current = true;
setCurrentVariantOverride( setCurrentVariantOverride(
variant ?? null, variant,
resolveInheritedVariantForModel(providerId, modelId, agentNameOverride), resolveInheritedVariantForModel(providerId, modelId, agentNameOverride),
); );
addRecentEffort(providerId, modelId, variant); addRecentEffort(providerId, modelId, variant ?? undefined);
const effectiveAgentName = agentNameOverride ?? resolveLiveAgentName(); const effectiveAgentName = agentNameOverride ?? resolveLiveAgentName();
if (currentSessionId && effectiveAgentName) { if (currentSessionId && effectiveAgentName) {
// `null`, not `undefined`: picking "Default" is a choice to record, saveAgentModelVariantForSession(currentSessionId, effectiveAgentName, providerId, modelId, variant);
// not the absence of one.
saveAgentModelVariantForSession(currentSessionId, effectiveAgentName, providerId, modelId, variant ?? null);
} }
}, [ }, [
addRecentEffort, addRecentEffort,
@@ -802,7 +816,7 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
setCurrentVariantOverride, setCurrentVariantOverride,
]); ]);
const applyModelSelectionWithVariant = React.useCallback((providerId: string, modelId: string, variant: string | undefined, agentNameOverride?: string | null) => { const applyModelSelectionWithVariant = React.useCallback((providerId: string, modelId: string, variant: string | null | undefined, agentNameOverride?: string | null) => {
const effectiveAgentName = agentNameOverride ?? resolveLiveAgentName() ?? undefined; const effectiveAgentName = agentNameOverride ?? resolveLiveAgentName() ?? undefined;
const result = tryApplyModelSelection(providerId, modelId, effectiveAgentName); const result = tryApplyModelSelection(providerId, modelId, effectiveAgentName);
if (result !== 'applied') { if (result !== 'applied') {
@@ -874,15 +888,11 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
return; return;
} }
// The effort is not written again here: `applyModelSelectionWithVariant`
// above already recorded `historicalVariant` for this same agent and
// model, and a second write can only disagree with the first.
if (latestLoadedUserChoice.agent) { if (latestLoadedUserChoice.agent) {
saveSessionAgentSelection(currentSessionId, latestLoadedUserChoice.agent); saveSessionAgentSelection(currentSessionId, latestLoadedUserChoice.agent);
saveAgentModelVariantForSession(
currentSessionId,
latestLoadedUserChoice.agent,
latestLoadedUserChoice.providerID,
latestLoadedUserChoice.modelID,
historicalVariant,
);
} }
saveSessionModelSelection(currentSessionId, latestLoadedUserChoice.providerID, latestLoadedUserChoice.modelID); saveSessionModelSelection(currentSessionId, latestLoadedUserChoice.providerID, latestLoadedUserChoice.modelID);
latestLoadedUserChoiceRestoreRef.current = restoreKey; latestLoadedUserChoiceRestoreRef.current = restoreKey;
@@ -900,7 +910,6 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
getSessionModelSelection, getSessionModelSelection,
resolveModelVariantSelection, resolveModelVariantSelection,
saveSessionAgentSelection, saveSessionAgentSelection,
saveAgentModelVariantForSession,
saveSessionModelSelection, saveSessionModelSelection,
]); ]);
@@ -1184,7 +1193,9 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
const handleVariantSelect = React.useCallback((variant: string | undefined) => { const handleVariantSelect = React.useCallback((variant: string | undefined) => {
if (currentProviderId && currentModelId) { if (currentProviderId && currentModelId) {
commitVariantSelectionForModel(currentProviderId, currentModelId, variant); // Picked in the effort menu, so no effort means the user picked
// "Default" — a choice, recorded as `null`.
commitVariantSelectionForModel(currentProviderId, currentModelId, variant ?? null);
} }
}, [commitVariantSelectionForModel, currentModelId, currentProviderId]); }, [commitVariantSelectionForModel, currentModelId, currentProviderId]);
@@ -1246,8 +1257,10 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
) => { ) => {
try { try {
const effectiveAgentName = options?.agentName ?? resolveLiveAgentName() ?? undefined; const effectiveAgentName = options?.agentName ?? resolveLiveAgentName() ?? undefined;
// `applyVariant` is only set when the user adjusted the effort in
// the model picker, so no effort means an explicit "Default".
const result = options?.applyVariant const result = options?.applyVariant
? applyModelSelectionWithVariant(providerId, modelId, options.variant, effectiveAgentName) ? applyModelSelectionWithVariant(providerId, modelId, options.variant ?? null, effectiveAgentName)
: tryApplyModelSelection(providerId, modelId, effectiveAgentName); : tryApplyModelSelection(providerId, modelId, effectiveAgentName);
if (result !== 'applied') { if (result !== 'applied') {
if (result === 'provider-missing') { if (result === 'provider-missing') {
@@ -1604,8 +1617,10 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
} }
} }
const handleMobileModelApply = (providerId: string, modelId: string, variant: string | undefined) => { const handleMobileModelApply = (providerId: string, modelId: string, variant: string | null | undefined) => {
const result = applyModelSelectionWithVariant(providerId, modelId, variant); // Chosen in the mobile model sheet, and the row already showed this
// effort: no effort there means the user is applying "Default".
const result = applyModelSelectionWithVariant(providerId, modelId, variant ?? null);
if (result !== 'applied') { if (result !== 'applied') {
if (result === 'provider-missing') { if (result === 'provider-missing') {
console.error('[ModelControls] Provider not available for selection:', providerId); console.error('[ModelControls] Provider not available for selection:', providerId);
@@ -1642,7 +1657,8 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
const variantOptions = getModelVariantOptions(providerId, modelId); const variantOptions = getModelVariantOptions(providerId, modelId);
const hasVariants = variantOptions.length > 0; const hasVariants = variantOptions.length > 0;
const resolvedVariant = resolveModelVariantSelection(providerId, modelId); const resolvedVariant = resolveModelVariantSelection(providerId, modelId);
const variantLabel = hasVariants ? formatEffortLabel(resolvedVariant) : null; // Both an explicit "Default" and no choice at all read as "Default".
const variantLabel = hasVariants ? formatEffortLabel(resolvedVariant ?? undefined) : null;
const isExpanded = expandedMobileModelKey === rowKey; const isExpanded = expandedMobileModelKey === rowKey;
const inlineVariantOptions = [undefined, ...variantOptions].slice(0, MAX_INLINE_MOBILE_VARIANT_OPTIONS); const inlineVariantOptions = [undefined, ...variantOptions].slice(0, MAX_INLINE_MOBILE_VARIANT_OPTIONS);
const hasVariantOverflow = inlineVariantOptions.length < variantOptions.length + 1; const hasVariantOverflow = inlineVariantOptions.length < variantOptions.length + 1;
@@ -1947,7 +1963,8 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
}; };
const handleSelect = (variant: string | undefined) => { const handleSelect = (variant: string | undefined) => {
const result = applyModelSelectionWithVariant(targetProviderId, targetModelId, variant); // Chosen in the mobile effort panel: no effort means "Default".
const result = applyModelSelectionWithVariant(targetProviderId, targetModelId, variant ?? null);
if (result !== 'applied') { if (result !== 'applied') {
return; return;
} }
+7
View File
@@ -232,6 +232,13 @@ per-session selection store records an explicit `Default` (as `null`) instead of
clearing the entry — a cleared entry is indistinguishable from never having clearing the entry — a cleared entry is indistinguishable from never having
chosen, and the settings default wins again on the next agent or session switch. chosen, and the settings default wins again on the next agent or session switch.
Only a place where the user chose may write `null`. Restore paths — message
history, a preserved manual override — pass their own "found nothing" through
as `undefined`, because a session whose history carries no effort is not a
session where `Default` was picked. A restore that manufactures `null` latches
the session onto `Default`: `null` outranks the agent and settings defaults by
design, so the concrete effort it displaced can never come back.
Every write of `currentVariant` writes `currentVariantSelection` with it. They Every write of `currentVariant` writes `currentVariantSelection` with it. They
are one selection; updating only the effective value leaves the picker showing are one selection; updating only the effective value leaves the picker showing
one effort while sends carry another. one effort while sends carry another.