{"record":{"id":"b66fbe55f3e0d573","repo":"garrytan/gstack","slug":"invalid-heatmap-json-expected-object-e1-gr","errorCode":null,"errorMessage":"Invalid heatmap JSON. Expected object: '{\"@e1\":\"green\",\"@e3\":\"red\"}'","messagePattern":"Invalid heatmap JSON\\. Expected object: '(.+?)'","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"browse/src/snapshot.ts","lineNumber":495,"sourceCode":"    const VALID_COLORS = new Set(['green', 'yellow', 'red', 'blue', 'orange', 'gray']);\n    const COLOR_MAP: Record<string, { border: string; bg: string }> = {\n      green:  { border: '#00b400', bg: 'rgba(0,180,0,0.15)' },\n      yellow: { border: '#ffb400', bg: 'rgba(255,180,0,0.15)' },\n      red:    { border: '#ff0000', bg: 'rgba(255,0,0,0.15)' },\n      blue:   { border: '#0066ff', bg: 'rgba(0,102,255,0.15)' },\n      orange: { border: '#ff6600', bg: 'rgba(255,102,0,0.15)' },\n      gray:   { border: '#888888', bg: 'rgba(136,136,136,0.15)' },\n    };\n\n    let colorAssignments: Record<string, string>;\n    try {\n      const parsed = JSON.parse(opts.heatmap);\n      if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n        throw new Error('not an object');\n      }\n      colorAssignments = parsed;\n    } catch {\n      throw new Error('Invalid heatmap JSON. Expected object: \\'{\"@e1\":\"green\",\"@e3\":\"red\"}\\'');\n    }\n\n    // Validate colors\n    for (const [ref, color] of Object.entries(colorAssignments)) {\n      if (!VALID_COLORS.has(color)) {\n        throw new Error(`Invalid heatmap color \"${color}\" for ${ref}. Valid: ${[...VALID_COLORS].join(', ')}`);\n      }\n    }\n\n    try {\n      const boxes: Array<{ ref: string; box: { x: number; y: number; width: number; height: number }; color: string }> = [];\n      for (const [refKey, color] of Object.entries(colorAssignments)) {\n        const cleanRef = refKey.startsWith('@') ? refKey.slice(1) : refKey;\n        const entry = refMap.get(cleanRef);\n        if (!entry) continue; // Skip refs not found on page\n        try {\n          const box = await entry.locator.boundingBox({ timeout: 1000 });\n          if (box) {","sourceCodeStart":477,"sourceCodeEnd":513,"githubUrl":"https://github.com/garrytan/gstack/blob/94993f74012782fd94416dd44b8314f6363a13a4/browse/src/snapshot.ts#L477-L513","documentation":"Thrown when opts.heatmap cannot be parsed into a plain JSON object. The value is run through JSON.parse and then type-checked: anything that is null, an array, a primitive, or syntactically invalid JSON all funnel into this single message. The expected shape is an object whose keys are refs and values are color names.","triggerScenarios":"Passing -H/--heatmap a string that is not valid JSON (e.g. `@e1=red`), valid JSON of the wrong type (e.g. `[\"@e1\",\"red\"]` or `'\"green\"'`), or `null`.","commonSituations":"Shell quoting that strips the braces (`-H {@e1:red}`); passing the heatmap as a path to a JSON file instead of the contents; typos like missing quotes around keys; copy-pasting YAML by mistake.","solutions":["Quote the entire JSON object in the shell and use double quotes inside: `-H '{\"@e1\":\"red\",\"@e3\":\"green\"}'`.","Validate the string with `jq .` or `JSON.parse` in a REPL before passing it.","Ensure the value parses to a plain object — not an array, not a string, not null.","If building the option programmatically, use `JSON.stringify(obj)` rather than hand-concatenating."],"exampleFix":"// before\nsnapshot(page, { heatmap: '@e1:red' }); // not JSON\n// after\nsnapshot(page, { heatmap: JSON.stringify({ '@e1': 'red' }) });","handlingStrategy":"validation","validationCode":"function asHeatmapObject(s: string): Record<string, string> {\n  let parsed: unknown;\n  try { parsed = JSON.parse(s); } catch { throw new Error('Invalid heatmap JSON. Expected object: {\"@e1\":\"green\"}'); }\n  if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {\n    throw new Error('Invalid heatmap JSON. Expected object: {\"@e1\":\"green\"}');\n  }\n  return parsed as Record<string, string>;\n}","typeGuard":"function isPlainObject(v: unknown): v is Record<string, unknown> {\n  return typeof v === 'object' && v !== null && !Array.isArray(v);\n}","tryCatchPattern":"try {\n  await snapshot(session, { heatmap: userInput });\n} catch (e: any) {\n  if (/^Invalid heatmap JSON/.test(e.message)) {\n    // surface to the user with the expected shape\n    throw new Error('heatmap must be a JSON object like {\"@e1\":\"red\"}');\n  } else throw e;\n}","preventionTips":["Always build the heatmap with JSON.stringify rather than string concatenation.","Validate with JSON.parse in a REPL or `jq .` before passing the CLI flag.","Shell-quote the whole JSON object with single quotes outside, double inside.","Reject array/primitive inputs at your own argument parser before reaching snapshot."],"tags":["input-validation","json","heatmap","argument-parsing"],"backgroundTag":null,"analyzedSha":"94993f74012782fd94416dd44b8314f6363a13a4","analyzedAt":"2026-08-12T04:06:23.140Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}