{"record":{"id":"77be41cc99fca270","repo":"cjpais/Handy","slug":"no-handler-for-setting-string-key","errorCode":null,"errorMessage":"No handler for setting: ${String(key)}","messagePattern":"No handler for setting: (.+?)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"src/stores/settingsStore.ts","lineNumber":332,"sourceCode":"      key: K,\n      value: Settings[K],\n    ) => {\n      const { settings, setUpdating } = get();\n      const updateKey = String(key);\n      const originalValue = settings?.[key];\n\n      setUpdating(updateKey, true);\n\n      try {\n        set((state) => ({\n          settings: state.settings ? { ...state.settings, [key]: value } : null,\n        }));\n\n        const updater = settingUpdaters[key];\n        if (updater) {\n          await updater(value);\n        } else if (key !== \"bindings\" && key !== \"selected_model\") {\n          console.warn(`No handler for setting: ${String(key)}`);\n        }\n      } catch (error) {\n        console.error(`Failed to update setting ${String(key)}:`, error);\n        if (settings) {\n          set({ settings: { ...settings, [key]: originalValue } });\n        }\n      } finally {\n        setUpdating(updateKey, false);\n      }\n    },\n\n    // Reset a setting to its default value\n    resetSetting: async (key) => {\n      const { defaultSettings } = get();\n      if (defaultSettings) {\n        const defaultValue = defaultSettings[key];\n        if (defaultValue !== undefined) {\n          await get().updateSetting(key, defaultValue as any);","sourceCodeStart":314,"sourceCodeEnd":350,"githubUrl":"https://github.com/cjpais/Handy/blob/fbd4e15fa14a721c66c57006ae110428b9e255b3/src/stores/settingsStore.ts#L314-L350","documentation":"settingsStore.updateSetting optimistically patches local Zustand state, then looks up a per-key async updater in the settingUpdaters map (settingsStore.ts line 76) to persist the value through a Tauri command. Keys 'bindings' and 'selected_model' are excluded because they are persisted through dedicated flows. For any other key with no map entry this warning fires: the UI shows the new value immediately, but the backend never persists it, so the setting silently reverts on the next reload.","triggerScenarios":"Adding a new field to the Settings/AppSettings type and wiring a toggle to updateSetting('newKey', v) without adding newKey: (v) => commands.changeNewKeySetting(v) to settingUpdaters; renaming a key in the type but not the map; a typo between the UI's key string and the map's key.","commonSituations":"Feature work adding a new preference; refactors that rename settings; review churn where the Rust command exists but the TS map entry was forgotten.","solutions":["Add an updater entry to settingUpdaters mapping the key to its persist command (e.g. new_option: (v) => commands.changeNewOptionSetting(v as boolean))","If the key is intentionally persisted elsewhere, add it to the exclusion condition alongside 'bindings' and 'selected_model' with a comment","Make coverage compile-enforced: type settingUpdaters as Record<keyof Settings, updater | symbol> and fill gaps with an explicit UNHANDLED marker so missing keys fail typecheck","Add a unit test enumerating Object.keys(defaultSettings) minus the exclusion list against Object.keys(settingUpdaters)"],"exampleFix":"// before: 'new_option' exists in Settings, toggle works, warning fires, value never persists\n// after: src/stores/settingsStore.ts\nconst settingUpdaters: { [K in keyof Settings]?: (value: Settings[K]) => Promise<unknown> } = {\n  // ...existing entries...\n  new_option: (value) => commands.changeNewOptionSetting(value as boolean),\n};","handlingStrategy":"type-guard","validationCode":"// Unit test: every persisted Settings key must have an updater (or be excluded)\nconst EXCLUDED = new Set([\"bindings\", \"selected_model\"]);\nconst missing = (Object.keys(defaultSettings) as (keyof Settings)[])\n  .filter((k) => !EXCLUDED.has(k) && !settingUpdaters[k]);\nif (missing.length) throw new Error(`No handler for setting: ${missing.join(\", \")}`);","typeGuard":"const hasUpdater = (key: keyof Settings): boolean =>\n  key === \"bindings\" || key === \"selected_model\" || Boolean(settingUpdaters[key]);","tryCatchPattern":null,"preventionTips":["Whenever you add a field to Settings, add its settingUpdaters entry in the same commit","Keep the exclusion list ('bindings', 'selected_model') documented with why they bypass the map","Treat this console.warn in dev as a bug: the setting currently does not persist"],"tags":["typescript","zustand","settings","persistence","developer-error"],"backgroundTag":"missing-settings-persistence-handler","analyzedSha":"fbd4e15fa14a721c66c57006ae110428b9e255b3","analyzedAt":"2026-08-17T10:29:55.597Z","contentChangedAt":"2026-08-17T10:29:55.597Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}