{"record":{"id":"85b49285dd6038e8","repo":"garrytan/gstack","slug":"invalid-heatmap-color-color-for-ref-valid","errorCode":null,"errorMessage":"Invalid heatmap color \"${color}\" for ${ref}. Valid: ${[...VALID_COLORS].join(', ')}","messagePattern":"Invalid heatmap color \"(.+?)\" for (.+?)\\. Valid: (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"browse/src/snapshot.ts","lineNumber":501,"sourceCode":"      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) {\n            const colors = COLOR_MAP[color] || COLOR_MAP.gray;\n            boxes.push({ ref: `@${cleanRef}`, box, color: JSON.stringify(colors) });\n          }\n        } catch {\n          // Element may be offscreen or hidden — skip\n        }","sourceCodeStart":483,"sourceCodeEnd":519,"githubUrl":"https://github.com/garrytan/gstack/blob/94993f74012782fd94416dd44b8314f6363a13a4/browse/src/snapshot.ts#L483-L519","documentation":"Thrown after a successful JSON parse when at least one value in the heatmap object is not in the allowed color set {green, yellow, red, blue, orange, gray}. The error names the offending color, the ref it was attached to, and the valid list so the caller can fix that one entry.","triggerScenarios":"Passing a heatmap object whose value is a hex code (`'#ff0000'`), a CSS color keyword outside the allow-list (`purple`), a typo (`gren`), or any non-string type.","commonSituations":"Reusing a CSS palette that includes colors not in the allow-list; case mismatch (`Red` vs `red` — the check is case-sensitive); a teammate adding a custom color without realizing the set is closed.","solutions":["Replace the offending value with one of: green, yellow, red, blue, orange, gray.","If you need a custom color, fork the COLOR_MAP and VALID_COLORS set in snapshot.ts — but note this changes the public contract.","Lower-case the values before serializing to avoid case mismatches.","Strip leading `#` and hex codes — the API takes color NAMES, not codes."],"exampleFix":"// before\nsnapshot(page, { heatmap: '{\"@e1\":\"#ff0000\",\"@e3\":\"purple\"}' });\n// after\nsnapshot(page, { heatmap: '{\"@e1\":\"red\",\"@e3\":\"gray\"}' });","handlingStrategy":"validation","validationCode":"const VALID_COLORS = new Set(['green','yellow','red','blue','orange','gray']);\nfunction validateHeatmapColors(obj: Record<string, string>): void {\n  for (const [ref, color] of Object.entries(obj)) {\n    if (!VALID_COLORS.has(color)) {\n      throw new Error(`Invalid heatmap color \"${color}\" for ${ref}. Valid: ${[...VALID_COLORS].join(', ')}`);\n    }\n  }\n}","typeGuard":"const isHeatmapColor = (c: string): boolean =>\n  ['green','yellow','red','blue','orange','gray'].includes(c);","tryCatchPattern":"try {\n  await snapshot(session, { heatmap: json });\n} catch (e: any) {\n  if (/^Invalid heatmap color/.test(e.message)) {\n    // normalize: clamp unknown colors to gray and retry\n    const fixed = remapColors(json, c => isHeatmapColor(c) ? c : 'gray');\n    await snapshot(session, { heatmap: JSON.stringify(fixed) });\n  } else throw e;\n}","preventionTips":["Restrict the color picker UI to the six allowed names.","Lower-case values before serializing.","Never use hex codes — the API takes names.","Add a unit test asserting every issued color is in the allow-list."],"tags":["input-validation","heatmap","color-palette","allow-list"],"backgroundTag":null,"analyzedSha":"94993f74012782fd94416dd44b8314f6363a13a4","analyzedAt":"2026-08-12T04:06:23.140Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}