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:
co-authored by
Claude Opus 5
parent
cb3bc3bd1b
commit
025cd3d46c
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
Reference in New Issue
Block a user