{"record":{"id":"42e5aa26649b3ce4","repo":"cjpais/Handy","slug":"failed-to-update-binding","errorCode":null,"errorMessage":"Failed to update binding","messagePattern":"Failed to update binding","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/stores/settingsStore.ts","lineNumber":370,"sourceCode":"                  [id]: {\n                    ...state.settings.bindings?.[id]!,\n                    current_binding: binding,\n                  },\n                },\n              }\n            : null,\n        }));\n\n        const result = await commands.changeBinding(id, binding);\n\n        // Check if the command executed successfully\n        if (result.status === \"error\") {\n          throw new Error(result.error);\n        }\n\n        // Check if the binding change was successful\n        if (!result.data.success) {\n          throw new Error(result.data.error || \"Failed to update binding\");\n        }\n      } catch (error) {\n        console.error(`Failed to update binding ${id}:`, error);\n\n        // Rollback on error\n        if (originalBinding && get().settings) {\n          set((state) => ({\n            settings: state.settings\n              ? {\n                  ...state.settings,\n                  bindings: {\n                    ...state.settings.bindings,\n                    [id]: {\n                      ...state.settings.bindings?.[id]!,\n                      current_binding: originalBinding,\n                    },\n                  },\n                }","sourceCodeStart":352,"sourceCodeEnd":388,"githubUrl":"https://github.com/cjpais/Handy/blob/98a4d80cce8ad41efec2a419b59d9e81229a35d7/src/stores/settingsStore.ts#L352-L388","documentation":"Frontend throw in settingsStore.updateBinding when the changeBinding Tauri command rejects (empty/whitespace binding, or shortcut fails validate_shortcut_for_implementation for the selected keyboard implementation) or returns success:false (binding id not present in settings or defaults, or the OS-level register_shortcut failed). The store then rolls back its optimistic binding update.","triggerScenarios":"Passing an unknown binding id (typo, removed setting) — backend returns success:false with 'Binding with id ... not found in defaults'; binding string empty or only modifiers so validation fails; register_shortcut fails at the OS layer (macOS accessibility permission missing, combo already grabbed by another app or reserved by the OS).","commonSituations":"macOS without Accessibility permission granted for keyboard shortcuts; hotkey conflicts with system/global shortcuts of other apps; frontend calling with an id from an older settings schema; user clearing the shortcut field.","solutions":["Surface result.data.error / the caught message — it names the exact backend reason","On macOS, grant Accessibility permission (System Settings > Privacy & Security > Accessibility) and retry","Try a different key combination to rule out an OS/app conflict","Verify the binding id matches one defined in the backend defaults","The store already rolled back the optimistic update — re-sync UI state from settings before retrying"],"exampleFix":"// before\nconst result = await commands.changeBinding(id, binding);\nif (!result.data.success) throw new Error(result.data.error || 'Failed to update binding');\n\n// after — keep the backend reason\nif (result.status === 'error') throw new Error(`changeBinding rejected: ${result.error}`);\nif (!result.data.success) throw new Error(result.data.error ?? `changeBinding failed for ${id}`);","handlingStrategy":"try-catch","validationCode":"// reject obviously invalid input before the round-trip\nif (!binding || binding.trim().length === 0) {\n  throw new Error('Binding cannot be empty');\n}\nif (!DEFAULT_BINDING_IDS.includes(id)) {\n  throw new Error(`Unknown binding id: ${id}`);\n}\nconst result = await commands.changeBinding(id, binding);","typeGuard":"type ChangeBindingResult =\n  | { status: 'error'; error: string }\n  | { status: 'ok'; data: { success: true; binding: Binding; error: null } }\n  | { status: 'ok'; data: { success: false; binding: null; error: string } };\n\nfunction isRejected(\n  r: ChangeBindingResult,\n): r is Extract<ChangeBindingResult, { status: 'error' }> | Extract<ChangeBindingResult, { status: 'ok'; data: { success: false } }> {\n  return r.status === 'error' || r.data.success === false;\n}","tryCatchPattern":"try {\n  const result = await commands.changeBinding(id, binding);\n  if (isRejected(result)) {\n    const reason = result.status === 'error' ? result.error : result.data.error;\n    throw new Error(reason ?? `changeBinding failed for ${id}`);\n  }\n} catch (error) {\n  // rollback (already implemented) then surface `error.message` to the UI toast\n}","preventionTips":["Disable the save action for empty or modifier-only shortcuts client-side","Check macOS Accessibility permission during onboarding before first binding change","Always prefer result.data.error over the generic fallback string so users see the backend reason"],"tags":["typescript","tauri-command","shortcut","settings","rollback"],"backgroundTag":"shortcut-registration-failed","analyzedSha":"98a4d80cce8ad41efec2a419b59d9e81229a35d7","analyzedAt":"2026-08-16T20:58:09.966Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}