---
status: awaiting_human_verify
trigger: "window-capture-flicker: Each time a window capture happens via the screen-timelapse MCP server, the target window flickers and some elements stop rendering."
created: 2026-04-12T00:00:00Z
updated: 2026-04-12T00:00:00Z
---

## Current Focus

hypothesis: Window.captureImageSync() uses PrintWindow/WM_PRINT API which forces the target window to repaint into a DC, causing visible flicker. Desktop capture uses BitBlt from screen buffer with no window interaction, hence no flicker. Fix: capture via monitor + crop to window bounds instead of using window capture API directly.
test: verify Window class exposes x(), y(), width(), height() for bounds; verify Monitor.fromPoint() exists; implement monitor-crop approach
expecting: monitor-crop approach should produce identical output without sending WM_PRINT to the window
next_action: implement the fix in window-target.ts

## Symptoms

expected: Window captures should be invisible to the user -- screenshots taken without any visual disruption to the target window
actual: The target window flickers during each capture frame, and some UI elements (especially in VoiceMeeter) stop rendering or appear blank. The effect is cumulative -- later frames in a sequence show more rendering artifacts than earlier ones.
errors: No error messages -- the capture completes successfully but the visual output shows the rendering disruption
reproduction: Use start_capture with target=window, window_title=VoiceMeeter, interval_ms=500, max_frames=6. Look at the resulting grid -- later frames (especially bottom-right) show elements that have stopped rendering.
started: Present since window capture was implemented in Phase 2. Desktop captures don't have this issue.

## Eliminated

## Evidence

- timestamp: 2026-04-12T00:10:00Z
  checked: window-target.ts capture implementation
  found: Uses win.captureImageSync() which calls node-screenshots Window.captureImageSync(). On Windows, this invokes PrintWindow API (WM_PRINT message) forcing the target window to repaint into a device context.
  implication: PrintWindow is a known cause of flicker for custom-rendered windows (DirectX, GDI+). Each call forces a repaint cycle visible to the user.

- timestamp: 2026-04-12T00:11:00Z
  checked: desktop-target.ts capture implementation
  found: Uses monitor.captureImageSync() which uses BitBlt to copy from the screen buffer. Does NOT interact with the target window at all.
  implication: This explains why desktop captures don't flicker -- they read from the existing screen buffer without forcing window repaints.

- timestamp: 2026-04-12T00:12:00Z
  checked: node-screenshots Window API (index.d.ts)
  found: Window class exposes x(), y(), width(), height(), currentMonitor() methods. Monitor class has captureImageSync() and static fromPoint(x, y). Image class has cropSync(x, y, w, h).
  implication: We can capture via the window's monitor and crop to window bounds, completely avoiding PrintWindow/WM_PRINT.

- timestamp: 2026-04-12T00:13:00Z
  checked: Web research on PrintWindow flicker
  found: PrintWindow is well-documented as causing flicker for certain window types. Windows apps using custom rendering (DirectX, GDI+) are especially affected. The cumulative degradation matches VoiceMeeter's behavior -- each WM_PRINT disrupts the render pipeline more.
  implication: Root cause confirmed. The fix is to use monitor capture + crop instead of direct window capture.

## Resolution

root_cause: Window.captureImageSync() uses the Windows PrintWindow API which sends WM_PRINT to the target window, forcing it to repaint into a device context. This causes visible flicker and cumulative rendering degradation in custom-rendered windows (like VoiceMeeter). Desktop capture uses BitBlt from the screen buffer and does not interact with the window, hence no flicker.
fix: Replace direct window capture (win.captureImageSync) with monitor capture + crop to window bounds. Capture the monitor the window is on, then crop to the window's screen coordinates. This avoids PrintWindow entirely.
verification: TypeScript compiles clean. All 49 tests pass. Awaiting human verification with VoiceMeeter window capture.
files_changed: [src/capture/targets/window-target.ts, src/capture/targets/window-region-target.ts, src/capture/targets/window-utils.ts]
