framewatch 0.3.0

Event-driven, change-triggered window capture that emits timestamped screenshots + metadata for AI agents.
Documentation
# Changelog

All notable changes to this project are documented here. The format is based on
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres
to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) (pre-1.0: breaking
changes bump the minor version).

## [Unreleased]

## [0.3.0] - 2026-06-14

### Added
- **`shot` subcommand** (from agent feedback): one-shot capture of a single
  settled frame to a chosen file. Optionally `--launch "<cmd>"` to spawn a
  program, capture *its* window (matched by PID), and kill it on exit. Writes to
  `--out-file`, prints the path on stdout, and exits non-zero if nothing settled
  before `--timeout` (`--settle-best-effort` writes the latest frame instead).
  Collapses launch → wait → capture → teardown into one command, with no session
  directory or timestamped glob.
- **Exact `--pid` window matching** (on `watch` and `shot`) and a `Target::ByPid`
  variant — avoids latching onto a stale window from an earlier run of the same
  exe on back-to-back captures.
- **Headless `--roi <X,Y,W,H>` crop**: capture, change detection, and saved
  images are all clipped to a pixel region — e.g. to drop host window chrome
  (titlebar / menu bar) around a captured app, without round-tripping through the
  GUI ROI editor. Backed by `Config::crop`, the `crop` / `crop_xywh` builder
  methods, and a public `RawFrame::crop`.

### Changed
- `Config` and `Target` are now `#[non_exhaustive]`; construct `Config` via
  `Config::builder()` / `Config::default()` (reading/writing existing fields and
  constructing `Target` variants are unaffected). This lets future config knobs
  and target kinds be added as non-breaking patch releases.

### Fixed
- CI: use `checked_div` instead of a manual `if count == 0` guard in
  `WorkingFrame::from_raw`, satisfying clippy's `manual_checked_ops` lint (Rust 1.96).
- Docs: clarified that `window.rect` is `[x, y, width, height]` (not
  `[left, top, right, bottom]`) in virtual-desktop pixels, and that a perfectly
  static target yields only the `initial` frame (pair `--until-settled` with
  `--duration` as a fallback bound).

### Internal
- CI pins the Rust toolchain (1.96.0) instead of tracking `@stable`, so a new
  compiler/clippy release can't turn CI red without a code change.

## [0.2.0] - 2026-06-14

### Fixed
- **Fullscreen / sustained-activity captures.** A surface that changes on every
  frame (e.g. a fullscreen video or game) never quiesced, so after the initial
  frame *no images were ever saved*. Added a `max_active_ms` keyframe (default
  5000 ms) so sustained activity still yields periodic captures.
- Implemented `fps_cap` (it was a documented-but-unused config knob): frames
  arriving faster than the cap are now dropped before the downsample pass.
- Windows backend: window geometry (`rect`/`client_rect`/`dpi`/`foreground`) in
  the timeline metadata is now refreshed during capture instead of being frozen
  at start, so it stays correct across resizes / fullscreen transitions.

### Added
- **Lifecycle flags for frictionless agent use** (from real agent feedback):
  `--wait <secs>` polls for the target window to appear (no launch-order race),
  and `--until-settled` / `--duration <secs>` / `--frames <n>` make `watch` a
  bounded one-shot that exits on its own. Backed by `Config::{wait_ms,
  stop_after_ms, stop_after_images, stop_after_settled}` and a new
  `watch_with(config, backend, sink)` for embedding with a custom backend.
- All-black frame detection: logs a one-time warning when the target is likely in
  exclusive fullscreen or showing DRM-protected content (which WGC renders black).
- `Engine::frames_dropped()` and the `max_active_ms` config / builder option.

### Hardened
- Buffer-size arithmetic in `encode` and `WorkingFrame::from_raw` now uses `usize`
  math to avoid `u32` overflow on very large (multi-4K) frames.
- GUI preview no longer panics if the frame mutex is poisoned.

## [0.1.0] - 2026-06-14

Initial release.

### Added
- Pure, backend-agnostic detection `Engine`: tile diffing, dHash dedup,
  per-tile/region volatility tracking, and an Idle/Active/Busy state machine.
- Event model + JSON contract: `CaptureEvent` / `CaptureMeta`, `timeline.jsonl`,
  and the `session.json` manifest.
- Sinks: `DirectorySink` (PNG + timeline + manifest + `README_FOR_AGENT.md`,
  with rotation), `ChannelSink`, and `CompositeSink`.
- Cross-platform `MockBackend` (replays in-memory frames or decoded PNGs).
- Windows Graphics Capture backend and window enumeration behind the `wgc`
  feature (`#[cfg(windows)]`), wrapping `windows-capture` 2.x.
- `framewatch` CLI (`windows`, `watch`, `gui`) behind the `cli` feature.
- eframe/egui GUI (window picker, live preview, ROI editor) behind the `gui`
  feature.
- Configuration via builder API and `framewatch.toml`.
- Scenario + golden tests covering static, spinner, volatile, dedup, and the
  full directory-sink pipeline.

[Unreleased]: https://github.com/dmoore-dwmmholdings/framewatch/compare/v0.3.0...HEAD
[0.3.0]: https://github.com/dmoore-dwmmholdings/framewatch/compare/v0.2.0...v0.3.0
[0.2.0]: https://github.com/dmoore-dwmmholdings/framewatch/compare/v0.1.0...v0.2.0
[0.1.0]: https://github.com/dmoore-dwmmholdings/framewatch/releases/tag/v0.1.0