{"record":{"id":"3d5cb9313dbc8b0b","repo":"nexu-io/open-design","slug":"brand-is-not-a-json-object","errorCode":null,"errorMessage":"brand is not a JSON object","messagePattern":"brand is not a JSON object","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/daemon/src/brands/validate.ts","lineNumber":52,"sourceCode":"\nfunction fontSpec(raw: unknown, fallbackFamily: string): BrandFontSpec {\n  const o = (raw ?? {}) as Record<string, unknown>;\n  return {\n    family: isStr(o.family) && o.family ? o.family : fallbackFamily,\n    fallbacks: strArr(o.fallbacks),\n    weights: Array.isArray(o.weights) ? o.weights.filter((w) => typeof w === 'number') : [400, 700],\n    ...(isStr(o.googleFontsUrl) && o.googleFontsUrl ? { googleFontsUrl: o.googleFontsUrl } : {}),\n    ...(isStr(o.notes) && o.notes ? { notes: o.notes } : {}),\n  };\n}\n\n/**\n * Validate + normalize a brand object. Throws with a precise message on\n * unrecoverable problems (missing name/colors); fills sensible defaults for\n * everything optional so a slightly-sloppy input still renders.\n */\nexport function validateBrand(raw: unknown, sourceUrl: string): Brand {\n  if (!raw || typeof raw !== 'object') throw new Error('brand is not a JSON object');\n  const o = raw as Record<string, unknown>;\n\n  if (!isStr(o.name) || !o.name.trim()) throw new Error('brand: missing required `name`');\n\n  const rawColors = Array.isArray(o.colors) ? o.colors : [];\n  const colors: BrandColor[] = [];\n  for (const c of rawColors) {\n    if (!c || typeof c !== 'object') continue;\n    const co = c as Record<string, unknown>;\n    const role = BRAND_COLOR_ROLES.includes(co.role as BrandColorRole)\n      ? (co.role as BrandColorRole)\n      : null;\n    const hex = isStr(co.hex) && /^#[0-9a-fA-F]{6}$/.test(co.hex) ? co.hex.toLowerCase() : null;\n    if (!role || !hex) continue;\n    colors.push({\n      role,\n      hex,\n      oklch: isStr(co.oklch) ? co.oklch : '',","sourceCodeStart":34,"sourceCodeEnd":70,"githubUrl":"https://github.com/nexu-io/open-design/blob/5be4028344c2eb4c667c5a97bda8f750c5597ef7/apps/daemon/src/brands/validate.ts#L34-L70","documentation":"Thrown by validateBrand when the input is not a JSON object — null, an array, a primitive (string/number/bool), or undefined. validateBrand normalizes agent-produced or hand-authored brand JSON, and the very first invariant is that it must be an object. Earlier extraction usually runs the input through extractJsonBlock, which can yield null/array output.","triggerScenarios":"validateBrand(parsed, sourceUrl) where parsed came from JSON.parse of an LLM-produced text block that yielded an array ('[{...}]'), the string 'null', a bare primitive, or null because no JSON block was found.","commonSituations":"LLM wraps the brand in a list '[...]' or returns the literal 'null'; agent emits prose instead of JSON and extractJsonBlock falls back to grabbing text between { and } that is not an object; a hand-authored brand.json file containing a top-level array; an empty file.","solutions":["Pre-check the parsed value with `typeof raw === 'object' && raw !== null && !Array.isArray(raw)` before validateBrand.","Improve the extraction prompt to require a single JSON object, not a list, and to omit null.","If extraction yields an array, take the first element only after validating it is an object.","On validateBrand failure, re-prompt the agent with the error message rather than crashing the extraction."],"exampleFix":"// before\nconst brand = validateBrand(parsed, sourceUrl);\n// after\nif (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {\n  throw new Error('expected a brand JSON object from the extractor');\n}\nconst brand = validateBrand(parsed, sourceUrl);","handlingStrategy":"type-guard","validationCode":"if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {\n  throw new Error('expected a brand JSON object from the extractor');\n}","typeGuard":"const isPlainObject = (v: unknown): v is Record<string, unknown> =>\n  typeof v === 'object' && v !== null && !Array.isArray(v);","tryCatchPattern":null,"preventionTips":["Run extracted text through extractJsonBlock and check the result is a plain object.","Prompt the extractor to emit a single JSON object, not a list or null.","Re-prompt the agent with the validation error rather than crashing."],"tags":["brand","validation","schema","llm-output"],"backgroundTag":null,"analyzedSha":"5be4028344c2eb4c667c5a97bda8f750c5597ef7","analyzedAt":"2026-08-12T12:03:58.812Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}