{"record":{"id":"220574da833ccc40","repo":"heygen-com/hyperframes","slug":"zoom-selector-matched-no-element-target-selec","errorCode":null,"errorMessage":"--zoom selector matched no element: ${target.selector}","messagePattern":"--zoom selector matched no element: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/cli/src/capture/captureCompositionFrame.ts","lineNumber":468,"sourceCode":"// clamping leaves a pixel-wide remnant. Either way the crop would be a sliver\n// that tells an agent nothing, so the final clamped region is what's guarded\n// and callers skip the frame on null. Explicit x,y,w,h regions stay literal.\nconst MIN_CROP_REGION_PX = 8;\n\nexport async function resolveCropRegion(\n  page: ZoomSelectorPage,\n  target: ZoomTarget,\n  canvas: CropCanvas,\n  paddingPx = DEFAULT_ZOOM_PADDING_PX,\n): Promise<CropRegion | null> {\n  if (target.kind === \"region\") return clampCropRegion(target.region, canvas);\n  const bbox = await page.evaluate((selector) => {\n    const element = document.querySelector(selector);\n    if (!element) return null;\n    const rect = element.getBoundingClientRect();\n    return { x: rect.x, y: rect.y, width: rect.width, height: rect.height };\n  }, target.selector);\n  if (!bbox) throw new Error(`--zoom selector matched no element: ${target.selector}`);\n  const region = padCropRegion(bbox, canvas, paddingPx);\n  if (region.width < MIN_CROP_REGION_PX || region.height < MIN_CROP_REGION_PX) return null;\n  return region;\n}\n\nexport interface CropCapturePage {\n  viewport(): { width: number; height: number; deviceScaleFactor?: number } | null;\n  setViewport(viewport: {\n    width: number;\n    height: number;\n    deviceScaleFactor?: number;\n  }): Promise<void>;\n  screenshot(options: { clip: CropRegion; type: \"png\"; omitBackground: true }): Promise<Uint8Array>;\n}\n\n/**\n * Capture a high-density crop of `region`: raise `deviceScaleFactor` to\n * `scale`, take a clip screenshot, then restore the original viewport.","sourceCodeStart":450,"sourceCodeEnd":486,"githubUrl":"https://github.com/heygen-com/hyperframes/blob/c2996c8626135db5253519359d8a063d3bafad8d/packages/cli/src/capture/captureCompositionFrame.ts#L450-L486","documentation":"Thrown by resolveCropRegion() when a --zoom target of kind 'selector' is resolved but document.querySelector(selector) returned null at capture time — the element did not exist in the DOM when the frame was sampled. The pipeline deliberately fails loud rather than silently falling back to a full-frame crop, because a silent fallback would produce a misleading zoom that looks like a bug elsewhere. The offending selector is included.","triggerScenarios":"A --zoom selector target is evaluated via page.evaluate; document.querySelector(target.selector) returns null. Happens when the element is conditionally rendered, behind a fragment that hasn't mounted yet, mistyped, or removed from the composition since the selector was authored.","commonSituations":"Selector references a class/ID that was renamed; element exists only after a fragment reveal that hasn't fired at the sampled timecode; typo in the selector; element is inside a shadow DOM that querySelector can't cross; element only appears in a different slide/scene.","solutions":["Verify the selector matches at the capture timecode: open the composition in a browser at that frame and run document.querySelector('<selector>') in devtools.","Use a selector that is stable across the timeline, or switch to an explicit region (--zoom x,y,w,h) if the element moves.","If the element is shadow-DOM scoped, use a selector that pierces it or restructure the composition.","Confirm the element exists at the exact frame being captured (not just at t=0)."],"exampleFix":"// before — selector does not match at capture time\n--zoom .stat-value  // element only appears after a fragment reveal\n// option A: use a stable selector\n--zoom #hero-title\n// option B: use an explicit region (in px)\n--zoom 120,80,640,360","handlingStrategy":"validation","validationCode":"// Before capture, verify the selector resolves at the target timecode in a preview run.\nconst exists = await page.evaluate((sel) => !!document.querySelector(sel), selector);\nif (!exists) {\n  throw new Error(`Selector not present at this timecode: ${selector}`);\n}","typeGuard":null,"tryCatchPattern":"try {\n  await resolveCropRegion(page, target, canvas);\n} catch (err) {\n  if (/--zoom selector matched no element/i.test((err as Error).message)) {\n    // fall back to an explicit region or skip the frame\n  }\n  throw err;\n}","preventionTips":["Use stable, ever-present selectors for --zoom targets.","When a target only appears after a fragment reveal, sample at a later timecode or use an explicit region.","For shadow DOM, use piercing selectors or restructure the composition."],"tags":["capture","zoom","selector","dom","validation"],"backgroundTag":null,"analyzedSha":"c2996c8626135db5253519359d8a063d3bafad8d","analyzedAt":"2026-08-12T22:18:56.877Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}