ui: read persisted settings before the API key probe (#27365)
The route loads run ahead of the root layout script, so validateApiKey read the settings store while it still held factory defaults and probed /props without the stored key. initStores() now hands the same startup promise to every caller and the chat loads await it before probing. The one-time admin baseline no longer overwrites a key the user has already set: on a first visit the config carries factory values only, so a diverging key comes from the user and wins.
This commit is contained in:
@@ -1,10 +1,15 @@
|
||||
/**
|
||||
* Explicit store initialization, called once from the root layout.
|
||||
* Explicit store initialization, run once and shared by every caller.
|
||||
*
|
||||
* Order matters: migrations run first because they rename and rewrite
|
||||
* localStorage keys, so every store that reads localStorage initializes
|
||||
* only after they complete. Constructors and module-level side effects
|
||||
* stay empty so import order can no longer change startup behavior.
|
||||
*
|
||||
* The returned promise resolves once the persisted state is in memory, which
|
||||
* route loads await before reading settings: they run ahead of the root layout
|
||||
* script. The conversation list loads in the background, awaited by the chat
|
||||
* page that renders it.
|
||||
*/
|
||||
|
||||
// direct imports, not via the barrel, to avoid circular deps
|
||||
@@ -16,19 +21,20 @@ import { versionStore } from './version.svelte';
|
||||
import { browser } from '$app/environment';
|
||||
import { MigrationService } from '$lib/services/migration.service';
|
||||
|
||||
let started = false;
|
||||
let startup: Promise<void> | null = null;
|
||||
|
||||
export async function initStores(): Promise<void> {
|
||||
if (!browser || started) return;
|
||||
export function initStores(): Promise<void> {
|
||||
if (!browser) return Promise.resolve();
|
||||
|
||||
started = true;
|
||||
startup ??= (async () => {
|
||||
await MigrationService.runAllMigrations();
|
||||
|
||||
await MigrationService.runAllMigrations();
|
||||
settingsStore.initialize();
|
||||
permissionsStore.initialize();
|
||||
toolsStore.initialize();
|
||||
void versionStore.initialize();
|
||||
void conversationsStore.init();
|
||||
})();
|
||||
|
||||
settingsStore.initialize();
|
||||
permissionsStore.initialize();
|
||||
toolsStore.initialize();
|
||||
void versionStore.initialize();
|
||||
|
||||
await conversationsStore.init();
|
||||
return startup;
|
||||
}
|
||||
|
||||
@@ -358,17 +358,24 @@ class SettingsStore {
|
||||
// UI settings are the admin's defaults for new users: applied once on
|
||||
// the first visit, never on later loads, so the user's config can
|
||||
// diverge. "Reset to Default" is the explicit way back to the baseline.
|
||||
// A first visit config carries factory values only, so a key that
|
||||
// already diverges here was set by the user before the baseline could
|
||||
// be reached, through the API key splash, and stays theirs.
|
||||
if (uiSettings && this.isFirstVisit) {
|
||||
this.isFirstVisit = false;
|
||||
|
||||
for (const [key, value] of Object.entries(uiSettings)) {
|
||||
if (!this.userOverrides.has(key) && value !== undefined) {
|
||||
setConfigValue(this.config, key, value);
|
||||
if (value === undefined || this.userOverrides.has(key)) continue;
|
||||
|
||||
// theme lives in mode-watcher, not just in config -> propagate
|
||||
if (key === SETTINGS_KEYS.THEME) {
|
||||
setMode(value as ColorMode);
|
||||
}
|
||||
if (getConfigValue(this.config, key) !== getConfigValue(SETTING_CONFIG_DEFAULT, key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
setConfigValue(this.config, key, value);
|
||||
|
||||
// theme lives in mode-watcher, not just in config -> propagate
|
||||
if (key === SETTINGS_KEYS.THEME) {
|
||||
setMode(value as ColorMode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user