Judge the config storage actually keeps, not the one on screen

savePerModelConfig normalizes before deciding whether a config is default, and
the runtime hands the settings page Speculative Decoding "auto", which
canonicalizes to null. The page judged the raw object instead, so a model sitting
at defaults looked non-default: turning on "Remember for this model" reported
saved while the local write had dropped the entry, reopening showed it as not
remembered, and the mirror sent the server a speculative_type "auto" override the
browser did not have. That disagreement between the two is the one thing the
mirror is written to avoid.

The page now normalizes once and uses that object for the default check, the
local write and the server mirror alike. Driving the real module under node: the
raw object reads as non-default, storage stores nothing, and only the normalized
reading agrees with storage.
This commit is contained in:
danielhanchen 2026-07-28 18:56:12 +00:00
commit 60203ed16c
3 changed files with 50 additions and 3 deletions

View file

@ -53,6 +53,7 @@ import {
floorMaxSeqLength,
isDefaultConfig,
normalizeMaxSeqLength,
normalizePerModelConfig,
resolveInitialConfig,
savePerModelConfig,
} from "../model-config/per-model-config";
@ -871,14 +872,23 @@ export function ModelConfigPage({
const effectiveAtBaseline = perModelConfigsEqual(effectiveConfig, baseline);
const effectivePersistenceOnly =
isActiveModel && effectiveAtBaseline && rememberChanged;
const defaultConfig = isDefaultConfig(effectiveRuntimeConfig);
// Storage's own shape, because savePerModelConfig judges the normalized
// object: the runtime hands this page Speculative Decoding "auto", which
// canonicalizes to null, so the raw one looked non-default. Remember then
// reported saved while the local write had dropped the entry as default, and
// the mirror below sent the server a "auto" override the browser did not
// have, which is exactly the disagreement that mirror must not create.
const normalizedRuntimeConfig = normalizePerModelConfig(
effectiveRuntimeConfig,
);
const defaultConfig = isDefaultConfig(normalizedRuntimeConfig);
let saveFailed = false;
const evicted: { modelId: string; ggufVariant: string | null }[] = [];
if (remember) {
saveFailed = !savePerModelConfig(
configId,
target.ggufVariant,
effectiveRuntimeConfig,
normalizedRuntimeConfig,
evicted,
);
} else {
@ -896,7 +906,7 @@ export function ModelConfigPage({
syncModelOverride(
configId,
target.ggufVariant,
remember ? effectiveRuntimeConfig : null,
remember ? normalizedRuntimeConfig : null,
);
}
// Saving can push the local map over budget and drop other models, whose server

View file

@ -479,6 +479,18 @@ function normalizeV1(partial: RawConfig): PerModelConfig {
};
}
/**
* A config in the exact shape storage keeps it in.
*
* Callers that decide anything from a config a user is still editing have to run
* it through this first: the UI carries sentinels storage does not, notably
* Speculative Decoding "auto", which canonicalizes to null. Judging the raw
* object calls a default config non-default.
*/
export function normalizePerModelConfig(raw: unknown): PerModelConfig {
return normalize(raw);
}
function normalize(raw: unknown): PerModelConfig {
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
return normalizeV1({});

View file

@ -912,3 +912,28 @@ def test_the_detail_card_also_gates_ollama_out_of_the_api_promise():
assert hub.count("LOCAL_MODEL_SOURCE.OLLAMA") == 2
assert "selectedModel.localSource !== LOCAL_MODEL_SOURCE.OLLAMA" in hub
assert "row.source !== LOCAL_MODEL_SOURCE.OLLAMA" in hub
def test_the_settings_page_judges_the_config_storage_actually_keeps():
"""savePerModelConfig normalizes before deciding, and the runtime hands this
page Speculative Decoding "auto", which canonicalizes to null. Judging the raw
object called a default config non-default, so Remember reported saved while
the local write had dropped the entry, and the mirror sent the server an
"auto" override the browser did not have."""
src = " ".join(
_read("features/model-picker/components/model-config-page.tsx").split()
)
assert (
"const normalizedRuntimeConfig = normalizePerModelConfig( effectiveRuntimeConfig, );"
in src
)
assert "const defaultConfig = isDefaultConfig(normalizedRuntimeConfig);" in src
# The same object goes to storage and to the server, or they disagree again.
assert "target.ggufVariant, normalizedRuntimeConfig, evicted," in src
assert "remember ? normalizedRuntimeConfig : null," in src
assert "isDefaultConfig(effectiveRuntimeConfig)" not in src
store = " ".join(_read("features/model-picker/model-config/per-model-config.ts").split())
assert "export function normalizePerModelConfig(" in store
assert "const normalized = normalize(config);" in store
assert "if (isDefaultConfig(normalized)) {" in store, "the rule this mirrors"