garrytan/gstack · error · Error
Invalid heatmap color "${color}" for ${ref}. Valid: ${[...VA
Error message
Invalid heatmap color "${color}" for ${ref}. Valid: ${[...VALID_COLORS].join(', ')} What it means
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.
Source
Thrown at browse/src/snapshot.ts:501
orange: { border: '#ff6600', bg: 'rgba(255,102,0,0.15)' },
gray: { border: '#888888', bg: 'rgba(136,136,136,0.15)' },
};
let colorAssignments: Record<string, string>;
try {
const parsed = JSON.parse(opts.heatmap);
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
throw new Error('not an object');
}
colorAssignments = parsed;
} catch {
throw new Error('Invalid heatmap JSON. Expected object: \'{"@e1":"green","@e3":"red"}\'');
}
// Validate colors
for (const [ref, color] of Object.entries(colorAssignments)) {
if (!VALID_COLORS.has(color)) {
throw new Error(`Invalid heatmap color "${color}" for ${ref}. Valid: ${[...VALID_COLORS].join(', ')}`);
}
}
try {
const boxes: Array<{ ref: string; box: { x: number; y: number; width: number; height: number }; color: string }> = [];
for (const [refKey, color] of Object.entries(colorAssignments)) {
const cleanRef = refKey.startsWith('@') ? refKey.slice(1) : refKey;
const entry = refMap.get(cleanRef);
if (!entry) continue; // Skip refs not found on page
try {
const box = await entry.locator.boundingBox({ timeout: 1000 });
if (box) {
const colors = COLOR_MAP[color] || COLOR_MAP.gray;
boxes.push({ ref: `@${cleanRef}`, box, color: JSON.stringify(colors) });
}
} catch {
// Element may be offscreen or hidden — skip
}View on GitHub (pinned to 94993f7401)
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.
Example fix
// before
snapshot(page, { heatmap: '{"@e1":"#ff0000","@e3":"purple"}' });
// after
snapshot(page, { heatmap: '{"@e1":"red","@e3":"gray"}' }); Defensive patterns
Strategy: validation
Validate before calling
const VALID_COLORS = new Set(['green','yellow','red','blue','orange','gray']);
function validateHeatmapColors(obj: Record<string, string>): void {
for (const [ref, color] of Object.entries(obj)) {
if (!VALID_COLORS.has(color)) {
throw new Error(`Invalid heatmap color "${color}" for ${ref}. Valid: ${[...VALID_COLORS].join(', ')}`);
}
}
} Type guard
const isHeatmapColor = (c: string): boolean => ['green','yellow','red','blue','orange','gray'].includes(c);
Try / catch
try {
await snapshot(session, { heatmap: json });
} catch (e: any) {
if (/^Invalid heatmap color/.test(e.message)) {
// normalize: clamp unknown colors to gray and retry
const fixed = remapColors(json, c => isHeatmapColor(c) ? c : 'gray');
await snapshot(session, { heatmap: JSON.stringify(fixed) });
} else throw e;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Invalid heatmap JSON. Expected object: '{"@e1":"green","@e3"
- Cannot resolve real path: ${heatmapPath} (${err.code})
- Invalid scope: ${s}. Valid: ${validScopes.join(', ')}
- rateLimit must be >= 0
- expiresSeconds must be >= 0 or null
AI-assisted analysis of garrytan/gstack@94993f7401 (2026-08-12).
Data as JSON: /api/errors/85b49285dd6038e8.
Report an issue: GitHub.