diff --git a/HANDOFF.md b/HANDOFF.md index 00f8a18..c335197 100644 --- a/HANDOFF.md +++ b/HANDOFF.md @@ -1,15 +1,9 @@ -# HANDOFF — 2026-09-18 (layer drag-reorder persistence fix committed locally — no push) +# HANDOFF — 2026-09-18 (drag-reorder persistence + recording-dialog height, both local — no push) ## Branch / Commit State -`main` HEAD = **layer drag-reorder now traps + persists** (local, this session — the new -`LayerReorderPersistenceTests`). Below it: - -- `75723ac` fix(webcam): offer the Web Cam row only when a camera is attainable (live lock) -- `9d00955` feat(webcam): app-default gate slice — per-scene offer, Add places default directly, - identity survives removal -- `1e4017d` feat(webcam): resource lifecycle startup slice — poll-on-start, single-cam lock, - persistent Layers alert +`main` HEAD = **Rename Recording dialog 20% taller + teardown unhooks the full-screen hook** (local). +Below it: the layer drag-reorder persistence fix (`LayerReorderPersistenceTests`), then webcam work. **No push yet.** Working tree clean. @@ -70,6 +64,22 @@ ScheduleSave()`, i.e. the same background-save path every other mutation uses. * calls the trap, pumps the dispatcher until the debounced save lands, asserts the DB `Source` SortOrder matches the in-memory `Elements` order. +## ✅ Committed — Rename Recording dialog taller + full-screen hook unhooked on shutdown (2026-09-18) + +- `RenameRecordingDialog.xaml` `Height` 230 → **276** (+20%, creator: textbox cut off). The fix is + real: content naturally needs ~225px, the old client area at 230 was ~193px (clipped), at 276 it's + ~239px (fits). **Good Dog test** `RenameRecordingDialogSizingTests`: shows the real dialog in the + RealApp host and asserts the file-name textbox bottom edge is inside the client area. The dialog's + `Icon` was changed from root-relative `/Assets/...` to the explicit `/ytLive;component/...` (the + form `MainWindow.xaml` already uses) — root-relative only resolves in production via + `Application.ResourceAssembly`, so dialogs couldn't be constructed by tests otherwise. The other + dialogs still use the fragile form; fix as they get tested. +- **Teardown leak fix (dependency, discovered by the above):** `MainViewModel.Shutdown()` now calls + `_fullScreenDetector.StopWatching()`. The global `EVENT_SYSTEM_FOREGROUND` hook was never unhooked, + so after the VM was collected the next foreground event invoked a **garbage-collected + `WinEventProc` delegate → process crash** ("Test Run Aborted" in the suite). Harmless in production + (exits the process) but fatal to the multi-window test host when a test shows/foregrounds a window. + ## ✅ Earlier committed — webcam resource lifecycle (startup slice, `1e4017d`) Startup poll + tri-state (`ValidateWebcamResourceStartupAsync` after `LoadLayout`): 0 → run on, @@ -84,9 +94,7 @@ claim — `CameraConflictProbe` reads process names only, no device handles; do ## ⚠️ Open items -- **RenameRecordingDialog too short (creator-reported, QUEUED next):** the recording save/confirm - dialog cuts off the file-name textbox — increase its height by ~20% (+ its one integration test, - Good Dog). +- **RenameRecordingDialog too short — DONE (see below).** - **Webcam take fix 2 of 2 — the 09:16 crash is UNFIXED (hard, untested):** hypothesis — native WMF death when `MediaCapture.Failed` fires mid-stream and `SafeStopAsync` (fire-and-forget) disposes reader+capture while the frame thread sits in `TryAcquireLatestFrame`/`Marshal.Copy` (outside the @@ -143,6 +151,10 @@ claim — `CameraConflictProbe` reads process names only, no device handles; do - ffmpeg/ffprobe: `/mnt/c/Program Files/Krita (x64)/bin/` with Windows paths. - `MyMistakes.md`: WINRT resource-allocation RECIPE, freeze-audit RECIPE, A/V sync recipe, deadline pacing, CoreMessaging DQ, WGC-CLIP + slice blocks. Grep before re-deriving. +- **Test-host crash (fixed 2026-09-18):** "callback was made on a garbage collected delegate … + `Win32FullScreenDetector+WinEventProc`" — `Shutdown()` now unhooks the foreground-event hook; never + re-introduce `StartWatching` without a matching teardown, and dialogs whose `Icon` is the + root-relative `/Assets/…` form can't be instantiated by tests (use `/ytLive;component/…`). - **Scene.Elements layer tests:** any seeded scene with `HasBackground=1` loads a healed `Background` element pinned at `Elements[0]` (created by `EnsureBackground` on `StagedScene` set) — compute expected layer orders from the live collection, never hardcode indices. diff --git a/RenameRecordingDialog.xaml b/RenameRecordingDialog.xaml index 9de5066..763f8b3 100644 --- a/RenameRecordingDialog.xaml +++ b/RenameRecordingDialog.xaml @@ -1,8 +1,8 @@ diff --git a/ViewModels/MainViewModel.cs b/ViewModels/MainViewModel.cs index 585ecf2..bb6ba9a 100644 --- a/ViewModels/MainViewModel.cs +++ b/ViewModels/MainViewModel.cs @@ -418,6 +418,7 @@ public partial class MainViewModel : ViewModelBase { _saveDebounce?.Stop(); SaveLayoutNow(); + _fullScreenDetector.StopWatching(); _audioMixer.Dispose(); _framePump.Dispose(); _cameraManager.Dispose(); diff --git a/ytLive.Tests/RenameRecordingDialogSizingTests.cs b/ytLive.Tests/RenameRecordingDialogSizingTests.cs new file mode 100644 index 0000000..19f201a --- /dev/null +++ b/ytLive.Tests/RenameRecordingDialogSizingTests.cs @@ -0,0 +1,49 @@ +using System.Windows; +using Xunit; + +namespace ytLive.Tests; + +/// +/// The Rename Recording modal (creator-reported, 2026-09-18) cut off the file-name +/// textbox: the fixed window was too short for its stacked content. The height is +/// bumped 20% (230 → 276); this test renders the real dialog and asserts the textbox +/// bottom edge lands inside the client area instead of clipping. +/// +[Collection("RealApp")] +public sealed class RenameRecordingDialogSizingTests +{ + private readonly RealAppHost _app; + + public RenameRecordingDialogSizingTests(RealAppHost app) => _app = app; + + [Fact] + public void DialogHeight_Bumps20Percent_AndNameBoxIsNotClipped() + { + _app.Run(Run); + } + + private void Run() + { + var dialog = new RenameRecordingDialog("rec-2026-09-18"); + try + { + Assert.Equal(276, dialog.Height); + + dialog.Show(); + dialog.UpdateLayout(); + + var box = dialog.NameBox; + Assert.True(box.ActualWidth > 0, "file-name textbox must have a real layout"); + var client = (FrameworkElement)dialog.Content; + var bottom = box.TransformToAncestor(client) + .TransformBounds(new Rect(box.RenderSize)).Bottom; + Assert.True(bottom <= client.ActualHeight, + $"textbox bottom ({bottom:0.##}) must sit inside the client area " + + $"({client.ActualHeight:0.##}) — the dialog is clipping the name box"); + } + finally + { + dialog.Close(); + } + } +} \ No newline at end of file