fix: model picker correctly updates on profile switch without flicker or raw injection
Root cause: three interacting bugs caused the model picker to show the wrong model or flicker after a profile switch. Bug 1 — syncTopbar() fought switchToProfile(). After switchToProfile() set the picker to the profile's model, syncTopbar() was called (via renderSessionList -> loadSession, then explicitly at the end) and overwrote it with S.session.model -- the old session's model. Fix: added S._pendingProfileModel flag. switchToProfile() sets it; syncTopbar() checks it first, applies the override, then clears it. S.session.model is also updated to the resolved value so subsequent syncTopbar() calls are consistent. Bug 2 — Raw option injected at top of list for mismatched model IDs. Profile configs store model IDs like 'claude-sonnet-4-6' (hermes-agent format: hyphens, no namespace prefix) but the dropdown has 'anthropic/claude-sonnet-4.6' (OpenRouter format: dots, with prefix). The old code did sel.value = id, found no match, then injected a new <option> at the top of the list -- creating a lowercase duplicate that didn't match any real provider group entry. Fix: _findModelInDropdown() normalises both sides (strip prefix, hyphens->dots, lowercase) and finds the best matching existing option. No new options are ever injected for profile switching. Bug 3 — populateModelDropdown() injected raw option on cold load. Same issue: if default_model from /api/models didn't exactly match a dropdown value, an extra option was added. Fixed by using _applyModelToDropdown() which only selects existing options. New helpers in ui.js: _findModelInDropdown(modelId, sel) -- smart fuzzy match, returns matched value _applyModelToDropdown(modelId, sel) -- sets picker, returns resolved value Tests: 426 passed, 0 failed.
This commit is contained in:
@@ -668,17 +668,18 @@ async function switchToProfile(name) {
|
||||
// Refresh model dropdown (profile may have different provider/models)
|
||||
_skillsData = null;
|
||||
await populateModelDropdown();
|
||||
// Apply profile's default model if provided
|
||||
if (data.default_model && $('modelSelect')) {
|
||||
$('modelSelect').value = data.default_model;
|
||||
if ($('modelSelect').value !== data.default_model) {
|
||||
// Model not in list — add it
|
||||
const opt = document.createElement('option');
|
||||
opt.value = data.default_model;
|
||||
opt.textContent = data.default_model.split('/').pop();
|
||||
$('modelSelect').insertBefore(opt, $('modelSelect').firstChild);
|
||||
$('modelSelect').value = data.default_model;
|
||||
// Apply profile's default model using the smart resolver (handles id mismatches
|
||||
// like 'claude-sonnet-4-6' vs 'anthropic/claude-sonnet-4.6' in the dropdown)
|
||||
if (data.default_model) {
|
||||
const sel = $('modelSelect');
|
||||
const resolved = _applyModelToDropdown(data.default_model, sel);
|
||||
const modelToUse = resolved || data.default_model;
|
||||
// Also update the current session's model so syncTopbar() doesn't fight us
|
||||
if (S.session) {
|
||||
S.session.model = modelToUse;
|
||||
}
|
||||
// Store as pending so syncTopbar skips its model override on the next call
|
||||
S._pendingProfileModel = modelToUse;
|
||||
}
|
||||
// Refresh workspace list (now profile-local)
|
||||
_workspaceList = null;
|
||||
|
||||
Reference in New Issue
Block a user