{"record":{"id":"cdb944fb8ee354b4","repo":"linshenkx/prompt-optimizer","slug":"favorite-entry-must-be-an-object","errorCode":null,"errorMessage":"Favorite entry must be an object","messagePattern":"Favorite entry must be an object","errorType":"validation","errorClass":"FavoriteValidationError","httpStatus":null,"severity":"error","filePath":"packages/core/src/services/favorite/storage-guards.ts","lineNumber":163,"sourceCode":"  if (softLimitExceeded && options?.warnOnSoftLimit) {\n    const logWarning = options.logWarning ?? console.warn\n    logWarning(\n      `favorites payload exceeds soft limit (${totalBytes} bytes > ${FAVORITES_SOFT_LIMIT_BYTES} bytes)`,\n    )\n  }\n\n  return {\n    totalBytes,\n    softLimitExceeded,\n  }\n}\n\nexport const normalizeFavoriteRecord = (\n  value: unknown,\n  fallbackTimestamp = Date.now(),\n): FavoritePrompt => {\n  if (!isPlainObject(value)) {\n    throw new FavoriteValidationError('Favorite entry must be an object')\n  }\n\n  const raw = value as Record<string, unknown>\n  const rawContent = typeof raw.content === 'string' ? raw.content : ''\n  if (!rawContent.trim()) {\n    throw new FavoriteValidationError('Favorite prompt content cannot be empty')\n  }\n\n  const content = rawContent\n  const metadata = isPlainObject(raw.metadata) ? { ...raw.metadata } : undefined\n  const originalContent = toTrimmedString(raw.originalContent)\n  const nextMetadata = originalContent\n    ? { ...(metadata || {}), originalContent }\n    : metadata\n\n  assertFavoriteMetadataHasNoInlineImages(nextMetadata)\n\n  let functionMode: FavoritePrompt['functionMode'] = isFunctionMode(raw.functionMode)","sourceCodeStart":145,"sourceCodeEnd":181,"githubUrl":"https://github.com/linshenkx/prompt-optimizer/blob/3e677b1d9f7e0493c142c175560531e7ae786dce/packages/core/src/services/favorite/storage-guards.ts#L145-L181","documentation":"normalizeFavoriteRecord requires its input to be a plain object (isPlainObject). Null, arrays, strings, numbers, or anything non-object throw FavoriteValidationError before any field normalization runs.","triggerScenarios":"Passing or reading from storage a favorites array entry that is null, a primitive, or an array instead of an object — normalization is used when loading persisted records.","commonSituations":"Persisted favorites edited manually in devtools, sync conflicts writing malformed entries, or schema migrations leaving non-object entries in the store.","solutions":["Inspect the persisted favorites array and remove or repair non-object entries","Run a one-time cleanup that filters entries with a plain-object check before loading","If data is unrecoverable, export what is valid, reset the store, and re-import"],"exampleFix":"// before\nconst favorites = raw.map(normalizeFavoriteRecord); // raw contains 'oops'\n\n// after\nconst isPlainObject = (v: unknown) => typeof v === 'object' && v !== null && !Array.isArray(v);\nconst favorites = raw.filter(isPlainObject).map(normalizeFavoriteRecord);","handlingStrategy":"type-guard","validationCode":"const isPlainObject = (v: unknown): v is Record<string, unknown> =>\n  typeof v === 'object' && v !== null && !Array.isArray(v);\nconst clean = raw.filter(isPlainObject); // drop nulls/primitives/arrays before normalize","typeGuard":"const isPlainObject = (v: unknown): v is Record<string, unknown> =>\n  typeof v === 'object' && v !== null && !Array.isArray(v);","tryCatchPattern":"try {\n  const fav = normalizeFavoriteRecord(value);\n} catch (e) {\n  if (e instanceof FavoriteValidationError && /must be an object/.test(e.message)) {\n    // skip or repair the malformed record\n  }\n}","preventionTips":["Filter non-object entries from persisted arrays before normalization","Validate writes so only object records reach storage","Run a schema sanity check after migrations or sync merges"],"tags":["validation","storage","corruption","favorites"],"backgroundTag":"schema-validation-failed","analyzedSha":"3e677b1d9f7e0493c142c175560531e7ae786dce","analyzedAt":"2026-08-27T21:29:16.709Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}