Docs: record the zoom box as TASK 52 and fix the task-numbering drift (Unit: zoom docs)

The zoom feature shipped without a task record while its index rows carried the
"TASK 51" label — which belongs to the webcam background effects (Remove + Blur).
For an honest catalogue and handoff:

- TASKS/task-52-zoom-box.md: feature provenance (creator-requested), the three
  2026-10-08 corrections (loupe -> anchored capture over the full composite drawn
  last -> never show the dim shading), shapes, implementation map, tests, DONE.
- TASKS.md: row 52 added (shipped); row 51 reworded — unblocked now that the
  zoom build shipped.
- TASK 51 -> TASK 52 in Models/index.md, ViewModels/index.md, and the two code
  comments that pointed at the zoom as "TASK 51".
- HANDOFF.md: pre-push readiness notes (5 commits ahead, push-safe).
This commit is contained in:
2026-10-08 08:52:36 -07:00
parent 948dd08214
commit fe80e8c994
7 changed files with 67 additions and 7 deletions
+48
View File
@@ -0,0 +1,48 @@
# TASK 52 — Zoom box (anchored 2× magnifier)
**Status:** ✅ Done — shipped 2026-10-08 (three creator corrections during the build).
## What it is
Right-clicking the live preview parks a movable **2× magnifier box** that shows a
**live, always-on magnified view of a FIXED capture region** — the box/2 window centred
on the click. The captured area is whatever was under the click; it stays the same no
matter where the box is dragged.
## Creator rulings (order received, 2026-10-08)
1. **Not a loupe.** The click fixes the capture; **moving the box never changes its
content**. First cut shipped `4b3d82f` as a box-following loupe — reworked in
`6bff21b` to anchored `CaptureX/Y` (drag = `UpdateZoomMove` moves box only).
2. **Content = every layer.** The box magnifies the **fully-composited frame**
(backdrop + every element incl. webcam), drawn **LAST, on top of everything**
(layer n+1) — `SceneCompositor` skips the active `ZoomSceneElement` in every layer
loop and `BlitZoom`s it at the end via `Services/Compositor/ZoomMagnifier`.
3. **Never show shading.** The dim rectangles around the box must never appear inside
it — the `DimRects` overlay is a sibling of `CanvasGrid`, never a descendant, so the
zoom box's `VisualBrush` can't pick it up (`948dd08`).
4. **Rectangle = 4:3** ("more of a 640×480 aspect ratio"); Circle square, Phone 9:16.
## Shapes (master 1920×1080; max box 0.7·frame)
| Shape | Box | Notes |
|---|---|---|
| Circle | square, min(maxW,maxH) | round clip |
| VerticalRectangle (Phone) | 9:16 | h = 0.7·1080 |
| HorizontalRectangle (Tablet, default) | **4:3** | w = h·4/3 |
Box and capture are each centred on the click and clamped independently.
## Implementation
- `ViewModels/MainViewModel.Zoom.cs` — activate/move/dismiss + dims; transient
`ZoomSceneElement` healed/mirrored (`HealZoomLayers`/`EnsureZoomLayer`, z-index 1, never persisted).
- `Services/Compositor/ZoomMagnifier.cs` — pure 2× bilinear over an already-composited
opaque buffer (temp snapshot via `ArrayPool`, optional round clip).
- `Models/ZoomSceneElement.cs` / `Models/ZoomState.cs` — box rect + `CaptureX/Y`.
- `Controls/PreviewPane.xaml(.cs)` — `ZoomBox` (sibling above `CanvasGrid`, brushes the
whole composed preview at the fixed capture, 2×) + `DimRectsLayer` (sibling above the
content, below the box) + `UpdateZoomBox`.
- Output crop always equals the tier — the box is never the crop source.
## Tests
`ZoomMagnifierTests` (money proof: box over blue shows green capture at 2× incl. an extra
red "layer"; round-clip) + `ZoomBoxInteractionTests` (aspects, capture anchor centred on the
click, move-moves-only-the-box, corner clamp, dims-layer-structure invariant).
Full suite 406/406 on a clean run at `948dd08`.