fix(preview): draw video layers, unlink Play from Loop, geometry last

Three reports from the creator, plus two bugs the real clip exposed.

1. A video layer was INVISIBLE in the preview. The pane's per-layer Image is Collapsed by
   default and switched on by a whitelist of DataTriggers; MediaSource matched none, so the clip
   decoded into a bitmap that nothing displayed. Same defect the alert box hit on 2026-09-26 and
   the scoreboard after it — now recorded at the top of docs/ai/ui-surface.md with a test that
   asserts the bound Image is Visible (`MediaPreviewDisplayTests`).

2. Play is no longer tied to Loop: the button is always enabled (creator ruling — Loop must not
   toggle Play). Loop still restarts a finished clip, which is the behaviour that was broken.

3. Geometry is now the LAST section of the properties list for every draggable element, with
   opacity at the top; the web source's duplicate geometry block is gone (it now uses the shared
   one, which had been sitting above the media panel).

4. The physical mouse-drag test is deleted. It injected real cursor movement, hijacked whatever
   machine ran it, and failed whenever the desktop was busy. The arithmetic it covered now lives
   in `Helpers/DragReorderMath.cs` — pure, and covered by `DragReorderMathTests` (threshold,
   up/down moves, no-op and out-of-range). The two persistence tests were already synthetic.

5. Real-clip check (creator's `test_greenscreen_moving_subject.mp4`, 1920×1080): the detector
   REJECTED it — a fully saturated green sits at value 1.0 and the detector's 0.98 brightness
   ceiling threw it away. White highlights are excluded by the saturation floor, which is the
   property that actually separates them, so the ceiling is gone. With it fixed, the clip is
   detected as green (high confidence), and keyed through our own pipe 97.8% of pixels go
   transparent. Measured: keying with the canonical #00B140 keys 0.0% of this clip — which is why
   the key colour comes from the measured screen.

Tests: 567 passed, 0 failed, clean build 0 warnings. The gate's scope check flags
`Assets/Videos/test_greenscreen_moving_subject.mp4` — the creator's working copy, deliberately not
committed; the fixture lives under ytLive.Tests/Fixtures.
This commit is contained in:
2026-11-05 09:38:18 -08:00
parent 242a13ca30
commit 0f4a035376
14 changed files with 401 additions and 183 deletions
+20
View File
@@ -1,9 +1,29 @@
## Preview pane — frame-source layers are a WHITELIST
`PreviewPane.xaml`'s per-layer `Image` is **Collapsed by default** and switched on by one DataTrigger
per layer type that renders a frame (`IsImageSource`, `IsChatBox`, `IsAlertBox`, `IsTicker`,
`IsScoreboard`, `IsTextOverlay`, `IsWebSource`, the webcam variants, and — since 2026-11-05 —
`IsMediaSource` + `IsRectShape`). **A new frame-source layer type that forgets its trigger is
invisible in the preview while its decode logs healthy.** That has now bitten the alert box
(2026-09-26), the scoreboard, and the video layer. Add the trigger in the same commit as the layer
type, and pin it with a test that asserts the bound `Image` is `Visible` — see
`ytLive.Tests/MediaPreviewDisplayTests.cs`.
# UI surface: control room, patterns & transitions
The control-surface layout, scene strip, preview rail, hotkeys, broadcast drawer, toasts,
transitions, brand logo, launch geometry, and the theme/properties-panel conventions.
Read before changing anything the creator looks at.
**Frame-source layers are drawn by a WHITELIST (2026-11-05).** `PreviewPane.xaml`'s per-layer `Image`
is `Collapsed` by default and switched on by one DataTrigger per layer type that renders a frame
(`IsImageSource`, `IsChatBox`, `IsAlertBox`, `IsTicker`, `IsScoreboard`, `IsTextOverlay`,
`IsWebSource`, webcam variants, and — since 2026-11-05 — `IsMediaSource` with `IsRectShape`).
**A new frame-source layer type that forgets its trigger is invisible in the preview while the
decode logs healthy.** This has now bitten the alert box (2026-09-26), the scoreboard, and the video
layer, so add the trigger in the same commit as the layer type, and pin it with a test that asserts
the bound `Image` is `Visible` (see `MediaPreviewDisplayTests`).
## Launch POSITION is derived, never restored (2026-10-04)
Creator ruling: the window must open in the **upper-left of its screen with ~75px padding**, because