{"record":{"id":"2e69be28ff63e9f7","repo":"linshenkx/prompt-optimizer","slug":"favorite-already-exists","errorCode":"FAVORITE_ALREADY_EXISTS","errorMessage":"${error.content}","messagePattern":"\\$\\{error\\.content\\}","errorType":"exception","errorClass":"FavoriteAlreadyExistsError","httpStatus":null,"severity":"warning","filePath":"packages/core/src/services/favorite/electron-proxy.ts","lineNumber":56,"sourceCode":"  }\n\n  private async invokeMethod<T>(method: string, ...args: any[]): Promise<T> {\n    this.ensureApiAvailable();\n    try {\n      const safeArgs = safeSerializeArgs(...args);\n      return await (window.electronAPI.favoriteManager as any)[method](...safeArgs);\n    } catch (error: any) {\n      // New i18n-style structured errors: pass through as-is so UI can translate via `code + params`.\n      if (typeof error?.code === 'string' && error.code.startsWith('error.')) {\n        throw toErrorWithCode(error)\n      }\n\n      // 将IPC错误转换为具体的错误类型\n      if (error.code === 'FAVORITE_NOT_FOUND') {\n        throw new FavoriteNotFoundError(error.id || '');\n      }\n      if (error.code === 'FAVORITE_ALREADY_EXISTS') {\n        throw new FavoriteAlreadyExistsError(error.content || '');\n      }\n      if (error.code === 'CATEGORY_NOT_FOUND') {\n        throw new FavoriteCategoryNotFoundError(error.id || '');\n      }\n      if (error.code === 'VALIDATION_ERROR') {\n        throw new FavoriteValidationError(error.message || '');\n      }\n      if (error.code === 'STORAGE_ERROR') {\n        throw new FavoriteStorageError(error.message || '');\n      }\n      // Legacy: category already exists\n      if (error.code === 'CATEGORY_ALREADY_EXISTS') {\n        throw new FavoriteValidationError(error.message || 'Category already exists')\n      }\n      // 标签相关错误\n      if (error.code === 'TAG_ALREADY_EXISTS') {\n        throw new FavoriteTagAlreadyExistsError(error.tag || '');\n      }","sourceCodeStart":38,"sourceCodeEnd":74,"githubUrl":"https://github.com/linshenkx/prompt-optimizer/blob/3e677b1d9f7e0493c142c175560531e7ae786dce/packages/core/src/services/favorite/electron-proxy.ts#L38-L74","documentation":"Main process reported FAVORITE_ALREADY_EXISTS; converted to FavoriteAlreadyExistsError with the conflicting content as the message. Duplicate favorites are rejected by the storage layer.","triggerScenarios":"addFavorite (or import) with content identical to an existing favorite triggers the uniqueness check in the Electron main process.","commonSituations":"User double-clicks a save button; retry after timeout re-submits; import file contains entries already stored; no client-side dedupe before submit.","solutions":["Catch FavoriteAlreadyExistsError and surface a 'already saved' message instead of failing","Debounce/disable the save button while a request is in flight","Dedupe against the current list before calling addFavorite","For imports, skip-or-update strategy rather than blind insert"],"exampleFix":"// before\nawait mgr.addFavorite(fav);\n// after\ntry { await mgr.addFavorite(fav); }\ncatch (e) { if (e instanceof FavoriteAlreadyExistsError) { toast('Already favorited'); return; } throw e; }","handlingStrategy":"try-catch","validationCode":"const dup = (await mgr.getFavorites()).some(f => f.content === fav.content);","typeGuard":"const isAlreadyExists = (e: unknown): e is FavoriteAlreadyExistsError => e instanceof FavoriteAlreadyExistsError;","tryCatchPattern":"try { await mgr.addFavorite(fav); } catch (e) { if (isAlreadyExists(e)) { showToast('Already favorited'); return; } throw e; }","preventionTips":["Disable submit while a save is in flight","Dedupe by content before submitting","Make add idempotent on the client"],"tags":["favorite","duplicate","ipc","electron"],"backgroundTag":"duplicate-entry-rejected","analyzedSha":"3e677b1d9f7e0493c142c175560531e7ae786dce","analyzedAt":"2026-08-27T21:29:16.709Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}