}
};
+const CONFIG_TYPES_MIGRATION_ID = 'config-type-normalization-v1';
+
+const configTypesMigration: Migration = {
+ id: CONFIG_TYPES_MIGRATION_ID,
+ description: 'Coerce legacy string-encoded booleans in persisted config to real booleans',
+
+ async run(): Promise<void> {
+ const configRaw = localStorage.getItem(CONFIG_LOCALSTORAGE_KEY);
+ if (configRaw === null) return;
+
+ const config = JSON.parse(configRaw);
+ let changed = false;
+
+ // Pre-schema configs persisted booleans as the strings "true"/"false", which the
+ // strict server schema now rejects. Coerce those back to real booleans. No config
+ // string field holds exactly "true"/"false", so the match is unambiguous.
+ for (const key of Object.keys(config)) {
+ if (config[key] === 'true') {
+ config[key] = true;
+ changed = true;
+ } else if (config[key] === 'false') {
+ config[key] = false;
+ changed = true;
+ }
+ }
+
+ if (changed) {
+ localStorage.setItem(CONFIG_LOCALSTORAGE_KEY, JSON.stringify(config));
+ }
+
+ if (import.meta.env.DEV && import.meta.env.VITE_DEBUG)
+ console.log(`[Migration] Config types: coerced string booleans (changed=${changed})`);
+ }
+};
+
const migrations: Migration[] = [
localStorageMigration,
idxdbMigration,
legacyMessageMigration,
themeMigration,
customJsonKeyMigration,
- mcpDefaultEnabledMigration
+ mcpDefaultEnabledMigration,
+ configTypesMigration
];
export const MigrationService = {
/** Load thinking-enabled default from localStorage */
private static loadThinkingDefaults(): boolean {
- if (typeof globalThis.localStorage === 'undefined') return false;
+ if (typeof globalThis.localStorage === 'undefined') return true;
try {
const raw = localStorage.getItem(THINKING_ENABLED_DEFAULT_LOCALSTORAGE_KEY);
- if (!raw) return false;
- const parsed = raw === 'true';
- return typeof parsed === 'boolean' ? parsed : false;
+ if (!raw) return true;
+ return raw === 'true';
} catch {
- return false;
+ return true;
}
}
}
this.pendingMcpServerOverrides = [];
- this.pendingThinkingEnabled = false;
+ this.pendingThinkingEnabled = ConversationsStore.loadThinkingDefaults();
this.activeConversation = conversation;
if (conversation.currNode) {