# Changelog

## 2026-10-31 - 01:17

### Added

- The explainer site is published: <https://gesture-control.niftylittletools.dev/> (GitLab Pages, built from `explainer/` by the new `.gitlab-ci.yml`), its `site` set for canonical links and the sitemap, and the README's **Live:** line.

## 2026-10-30 - 18:44

### Changed

- Repository: `gitlab.com/nifty-little-tools/desktop-apps/gesture-control`, in the README and the explainer.

## 2026-10-29 - 19:59

### Documentation

- README reordered to the workspace structure (`_templates/README.md`): the key line (proof of concept), Usage, Development before Privacy & security, Research as Credits, and the Licence section with the house attribution.

### Fixed

- `uv.lock` had recorded an older version of the project itself, which `uv sync --locked` refuses; relocked (one line, no dependency changed). `check_conformance.py` now checks it.

## 2026-10-28 - 21:56

### Added

- **Explainer site** (`explainer/`, global §11): a static Astro page in the house Cyanotype style that says what the app is, what it does, how to run it, and its privacy terms, written from this README. It is light by default with its theme remembered, has a toggleable intro and the privacy notice, and loads nothing from a third party. Built from the workspace's shared template (`_explainer/`), copied unchanged and checked by `check_conformance.py`; only `src/site.js` (its words) is this tool's own. It is published once the tool's domain is chosen (plan Phase 8). The README gains an "Explainer site" section.

## 2026-10-26 - 18:42

### Changed

- `requires-python` lowered to `>=3.12` and `.python-version` to 3.12 (global §8: two minors behind the latest). Nothing needs 3.13: all 16 tests pass on Python 3.12.14 in an isolated environment. `uv.lock` refreshed.
- README: Python 3.12.

### Added

- README `## Privacy & security`: camera frames stay in memory; one first-run model download; the calibration file is the only thing stored; UI Automation reads only a control's kind and bounding box, never its text.

### Security

- `pip-audit` over the lockfile: no known vulnerabilities.

### Known issue

- The local `.venv` is still the 3.13 one, because it was in use. Run `uv sync` once the app is closed, so uv rebuilds it on 3.12.

## 2026-10-26 - 16:41

### Added

- `--outline hand`: draws each hand's outline with its 21 key points around the ring, at a fixed size. The line between the thumb and index tips is a pinch gauge: grey when apart, amber when nearly pinched, cyan once the pinch holds. `--outline ring` (the default) keeps the rings only.

### Changed

- Pinch and fist shapes are measured on MediaPipe's 3D world landmarks instead of the flat camera image, so the camera's aspect ratio and a hand tilted toward the camera no longer bend the measurement.
- Only a curled index finger rules out a pinch. Two-finger pinches with the other fingers folded, and whole-hand pinches, now count.
- A pinch starts or ends only after three frames in a row agree, so one misread frame no longer drops a carried window or fires a stray click.

### Fixed

- Pinches often went unregistered: distances were taken on image coordinates that count sideways gaps about a quarter short on a 4:3 camera, and a curled middle finger was taken for a fist.

## 2026-10-23 - 18:44

### Added

- `--calibrate`: four corner targets per hand fit each hand's zone to your distance from the camera and your reach; saved to `%APPDATA%\gesture-control\calibration.json` and used on every run.
- Double click: two pinch-taps within 0.7 s land on the first tap's point; inside Windows' double-click time the second click pairs with the first, otherwise both clicks are sent 50 ms apart.
- `q` + `Enter` in the terminal quits.
- The overlay re-claims the top of the always-on-top band twice a second, so picture-in-picture players don't cover it.

### Changed

- Each hand has its own camera zone (defaults for a camera on the monitor about half a metre away), so both rings can reach the middle of the screen together.
- Closing with a fist now needs 1.5 s, the gesture model and the hand geometry to agree it's a fist, and the hand to stay still over the same window.

### Fixed

- A fist could close the window under the ring instantly: frames where the hand read as a pinch or peace sign skipped resetting the fist timer, so a later brief fist (or a camera-angle misread) completed an old count, on a window chosen back then.

## 2026-10-23 - 17:43

### Added

- Arming: an open palm held about half a second starts control; it stops two seconds after the hands leave view.
- Pinch-tap to click, with tap-or-drag decided by movement and time.
- Drop or flick a carried window onto an edge: top maximises, left/right snaps to that half, bottom minimises, with a live preview of where it lands.
- Two-finger (peace sign) scrolling, content following the hand.
- Fist held over a window for one second closes it (`WM_CLOSE`), with a cancellable countdown ring.
- Target snapping (bubble cursor): while hovering, the ring catches the nearest button, link, tab, list item or icon found through UI Automation, highlights it, and a tap clicks its centre.
- Whole-screen zoom with both hands pinched (Windows Magnification API), anchored between the hands and panning with them; it snaps back to exactly 1x and is always reset on exit.
- Two-hand window resize while the main hand carries a window.
- `--swap-hands`; `--preview` now shows frame rate, inference time, each hand's role and whether control is active.

### Changed

- Hand roles fixed: MediaPipe labels the user's right hand "Left" in the mirrored frame, so the right hand is now the main hand.
- Volume is a vertical fader on the other hand (pinch and slide) instead of a wrist twist, which turned the hand edge-on and lost tracking.
- Overlay rebuilt on PySide6: antialiased, per-pixel transparency, redrawn every camera frame instead of every 33 ms.
- Pointer smoothing is a One Euro filter instead of exponential smoothing.
- Input is sent with `SendInput` batches (move, act, move back), so the mouse returns to where it was.
- A carried window is anchored where the pinch began, so it doesn't lag the hand by the click-or-drag dead zone.

### Fixed

- Grabbing the Start menu, taskbar or desktop: only windows with a caption or resize frame can be carried.
- Maximised windows moved while still maximised: they now restore first. Fullscreen windows are not carried.

### Removed

- Peace-sign-held opens Start (click the Start button instead), and the wrist-twist volume dial.

## 2026-10-23 - 17:10

### Changed

- `gesture-cameras` now lists camera **names** with the ID `--camera` takes (via `cv2-enumerate-cameras`), marks physical (USB) cameras and the default, and opens nothing.
- `gesture-cameras -v` opens each camera the way the app does and adds USB IDs, device path, resolution and measured frame rate. Each probe runs in a child process, so virtual-camera driver logging is discarded and a camera that never answers is reported after 8 s instead of hanging.
- `gesture-control` without `--camera` uses the first physical camera, not ID 0 (often a virtual camera).

### Removed

- `gesture-cameras --max` (the camera list now comes from the system).

## 2026-10-23 - 16:57

### Added

- `gesture-cameras` command: lists the camera indices that open and deliver frames, with resolution; `--show` displays a snapshot from each to tell them apart.

## 2026-10-23 - 16:55

### Added

- Proof of concept: webcam hand tracking with the MediaPipe Tasks gesture recogniser (`mediapipe` 1.1.1; the legacy `mp.solutions` API that hand-volume-control used no longer exists).
- Right hand: pinch to grab and move the window under the hand ring; hold a peace sign to open Start.
- Left hand: pinch and twist as a volume dial.
- Transparent, click-through, always-on-top overlay that shows hand rings, the dial and a Start confirmation.
- uv project on Python 3.13, tests for the gesture maths.
