diff --git a/HANDOFF.md b/HANDOFF.md index df17483..b31c9bc 100644 --- a/HANDOFF.md +++ b/HANDOFF.md @@ -1,44 +1,40 @@ -# HANDOFF — 2026-10-10 (desktop-"lock" fix local, uncommitted) +# HANDOFF — 2026-10-10 (scene-order F-key fix + desktop-lock fix; both LOCAL, uncommitted) ## At a glance -- **Branch** main (no feature branch). Pushed through `5685c5d`; everything after is local, **not - pushed** (push at the creator's call). -- Three work units in the working tree: (1) the desktop-lock fix below, (2) the TASK 51 webcam unit, - (3) the creator's pinned-chat unit. Do NOT mix them in one commit. +- **Branch** main (no feature branch). Pushed through `5685c5d`; 18 commits local, **not pushed** + (push at the creator's call). +- Desktop-lock fix committed as `63bf844`; **scene-order fix below is uncommitted**. ## Branch - main (no feature branch). Pushed through `5685c5d` (TRAX toggle); everything after is local, **not pushed** (push at the creator's call). ## New work unit (this handoff) — READY TO VERIFY, do not commit yet -- **Fix** for the "desktop is locked once the app starts" report — `MainWindow.xaml.cs` + - `ViewModels/MainViewModel.Background.cs` (`ShowActivated = !FullscreenGameRunningNow`, see - `bugs.md` entry 2026-10-10). Scope-locked files: `MainWindow.xaml.cs`, - `ViewModels/MainViewModel.Background.cs`, `ViewModels/MainViewModel.cs` (detector seam line), - `ytLive.Tests/DesktopLockTests.cs` (new), `bugs.md`, this file. -- **Regression tests:** `DesktopLockTests` (3 tests) drive the rule through the NEW - `FullScreenDetectorOverride` test seam (the same seam pattern as `WindowEnumeratorOverride`) — - no real Win32 foreground state is touched: full-screen game at launch ⇒ `ShowActivated=false`; - normal launch ⇒ still `true`; `FullscreenGameRunningNow` reflects the detector. Full suite - **422 passed / 0 failed / 0 warnings**. Real-app reproduction still deferred to the creator. -- **To commit later (this unit only):** the two code files + the two memory files listed above. -- Verify with the creator first: launch while an exclusive-fullscreen game is the foreground — the - game must stay live and the app must open behind/alongside it without focus steal. +- **Fix** for the scene-strip / F-key ordering report — `ViewModels/MainViewModel.Scenes.cs` new + `ReorderScenesToCanonical()` (canonical F1-F5 order at load, renamed scenes after; next save + re-stamps SortOrder) called from `MainViewModel.cs:LoadLayout`; `ytLive.Tests/SceneOrderTests.cs` + (new, seeds a Chat-before-BRB DB and asserts order + F1-F5 labels + persisted SortOrder). + See `bugs.md` entry 2026-10-10. Scope-locked files: `ViewModels/MainViewModel.Scenes.cs`, + `ViewModels/MainViewModel.cs` (1 call line), `ytLive.Tests/SceneOrderTests.cs`, `bugs.md`, + `HANDOFF.md`. +- Full suite **423 passed / 0 failed / 0 warnings**. Root cause was the creator's own DB (Chat + SortOrder 2, BRB 3); the fix heals it on next launch regardless of DB history. +- **To commit later (this unit only):** the files listed above. Then the working tree is back to + just the creator's pinned-chat unit (NOT mine to commit). -## Dirty files — split by ownership -- **This work unit (COMMIT these):** `Services/BackgroundEffects/MaskSmoother.cs` (new), the smoother - wiring in `Services/CameraManager.cs`, `ytLive.Tests/CameraManagerTests.cs` (new integration test), - and the same-commit memory updates (`Services/index.md`, `ai.md`, `TASKS/task-51-webcam-bg-effects.md`, - `TASKS.md` row 51, and this file) — all ship in the SAME commit. -- **NOT mine — do NOT commit:** the in-flight "pinned chat" work unit (`Models/YouTube.cs`, +## Desktop-lock unit (COMMITTED 63bf844 — desktop "lock" fix) +- `ShowActivated = !FullscreenGameRunningNow` + `FullScreenDetectorOverride` seam + `DesktopLockTests` + (3 tests). Committed 2026-10-10. Creator still to verify in-game (launch while an + exclusive-fullscreen game owns the foreground — the game must stay live). + +## Dirty files — current reality (2026-10-10) +- **NOT mine — do NOT commit:** the creator's in-flight "pinned chat" unit (`Models/YouTube.cs`, `ViewModels/MainViewModel.Chat.cs`, untracked `Helpers/RelayCommandGeneric.cs`, and **its hunks in - `Controls/LeftPanel.xaml`** — only the webcam-properties region is TASK 51; the pinned-chat hunks - stayed unstaged/untouched), plus the creator's untracked `Assets/llamacasty*` art. -- ⚠️ Pre-commit: `scripts/verify.sh` runs `scripts/scope-check.sh` against ALL working-tree changes — - the not-mine files WILL flag. Pass them to the script as declared-known (checksums unchanged since - `5685c5d`) or let the script's output be reconciled before committing. + `Controls/LeftPanel.xaml`**), plus the creator's untracked `Assets/llamacasty*` art. +- ⚠️ Pre-commit (scene-order unit): `scripts/verify.sh` runs `scripts/scope-check.sh` against ALL + working-tree changes — the not-mine pinned-chat + art files WILL flag. Pass them as declared-known. -## What just happened — two commits +## What just happened — TASK 51 quality pass (committed 2026-10-09) 1. **Jitter fix (`3057069`) — `MaskSmoother`**: per-session blend 0.75×new + 0.25×previous at the ONE worker (`CameraManager.RunEffectWorker` → `session.Smoother.Smooth(TrySegment(source))`). State on `CameraSession`, NOT the segmenter (shared app-wide, contract-pinned to raw output). Null @@ -55,14 +51,14 @@ the 0.5 rounding (3 box passes dilute a spike) — use a step edge, not a spike, to measure blur. ## Next step -- Creator verifies the desktop-lock fix: while THE DIVISION 2 (or any exclusive-fullscreen game) is the - foreground window, launch the app — the game must stay live and the app must not yank focus. Also - confirm a normal launch is unchanged. Then I can commit the unit and run the full suite when the - mouse is free. +- Creator launches the app: the scene strip must show Starting/Live/BRB/Chat/Ending in order with + F1-F5 correct (the creator's DB had Chat before BRB; the load-time reorder heals it and the next + save re-stamps SortOrder). Also still to verify when convenient: the desktop-lock fix while an + exclusive-fullscreen game owns the foreground. Then I can commit the scene-order unit. ## Tests -- Full suite **422 passed / 0 failed / 0 skipped / 0 warnings** (2026-10-10; includes the 3 new - `DesktopLockTests`). Known env flakes remain `Mix_HonorsProviderGains…` (fails while system audio +- Full suite **423 passed / 0 failed / 0 skipped / 0 warnings** (2026-10-10; includes the new + `SceneOrderTests`). Known env flakes remain `Mix_HonorsProviderGains…` (fails while system audio plays) and `RealMouseDrag…` (needs an idle desktop). ## Also still open @@ -77,9 +73,4 @@ was never run and the thread is closed unless the user reopens it. - Blur-heaviness = fixed (above). Yellow-border regression = stale thread. - ⛔ Sudo password rotation pending (user; then update both password lines in `CREDENTIALS.md`). -- TASK 56 secret scan before `gramps/LlamaCasty` goes public. TASK 55 disk hygiene queued. - -## Tests -- Full suite **416 passed / 1 known env flake** at `5685c5d`; the new smoothing test passes - standalone (1 test run, Duration < 1 s after warm). 0 build warnings. Known environment flakes: - `Mix_HonorsProviderGains…` (fails while system audio plays), `RealMouseDrag…` (needs an idle desktop). \ No newline at end of file +- TASK 56 secret scan before `gramps/LlamaCasty` goes public. TASK 55 disk hygiene queued. \ No newline at end of file diff --git a/ViewModels/MainViewModel.Scenes.cs b/ViewModels/MainViewModel.Scenes.cs index c5dfb4e..114ff1a 100644 --- a/ViewModels/MainViewModel.Scenes.cs +++ b/ViewModels/MainViewModel.Scenes.cs @@ -196,6 +196,32 @@ public partial class MainViewModel public IEnumerable MissingScenes => SceneCatalog.All.Where(canonical => Scenes.All(s => !SceneCatalog.Is(s.Name, canonical))); + /// + /// The five canonical scenes render in F-key order (Starting, Live, BRB, Chat, + /// Ending = F1-F5), whichever order the persisted SortOrder rows ended up in — + /// an older session can leave Chat before BRB (seen on the creator's DB). + /// Renamed (non-canonical) scenes keep their loaded order AFTER the canonical + /// five. Stable , so one pass; the next + /// auto-save re-stamps each SortOrder to its corrected index. + /// + private void ReorderScenesToCanonical() + { + var target = Scenes.OrderBy(s => CanonicalIndex(s.Name)).ToList(); + for (var i = 0; i < target.Count; i++) + { + var current = Scenes.IndexOf(target[i]); + if (current != i) Scenes.Move(current, i); + } + } + + private static int CanonicalIndex(string? name) + { + if (name == null) return SceneCatalog.All.Length; + for (var i = 0; i < SceneCatalog.All.Length; i++) + if (SceneCatalog.Is(name, SceneCatalog.All[i])) return i; + return SceneCatalog.All.Length; + } + /// Only show the "+" button when one of the five canonical scenes is missing. public bool ShowAddScene => MissingScenes.Any(); diff --git a/ViewModels/MainViewModel.cs b/ViewModels/MainViewModel.cs index 4941fcd..c9aa948 100644 --- a/ViewModels/MainViewModel.cs +++ b/ViewModels/MainViewModel.cs @@ -411,6 +411,7 @@ public partial class MainViewModel : ViewModelBase foreach (var name in SceneCatalog.All) AddScene(name); } + ReorderScenesToCanonical(); HealBackgrounds(); HealZoomLayers(); RefreshHotkeyLabels(); diff --git a/bugs.md b/bugs.md index 5ee0b88..1ad29f8 100644 --- a/bugs.md +++ b/bugs.md @@ -1,5 +1,18 @@ # Bugs +## Scene strip / F-keys out of sequence (FIXED 2026-10-10) +Report: "the ordering of the 5 screens and their corresponding F-key is out of sequence." The strip +renders `Scenes ORDER BY SortOrder` but each tab's F-key label is name-derived, so the two can drift +when a session persists a non-canonical order (on the creator's DB: Chat SortOrder 2, BRB 3 — tabs +showed "Chat F4" then "BRB F3"). + +Fix (same commit): `MainViewModel.LoadLayout` calls the new +`ReorderScenesToCanonical()` after loading — the five canonical scenes render in F-key order +(Starting, Live, BRB, Chat, Ending = F1-F5), renamed (non-canonical) scenes keep their loaded order +after them; the next auto-save re-stamps SortOrder to the corrected index. One integration test +(`SceneOrderTests`) seeds a Chat-before-BRB DB and asserts the loaded order + F1-F5 labels, then that +a save persists the corrected SortOrder (fresh load keeps canonical order). + ## Desktop "locked" after the app starts while a fullscreen game is running (FIXED 2026-10-10) Report: once the app is started, the desktop is "locked" — the mouse can no longer interact with it — which made streaming live gameplay impossible. diff --git a/ytLive.Tests/SceneOrderTests.cs b/ytLive.Tests/SceneOrderTests.cs new file mode 100644 index 0000000..056c3f1 --- /dev/null +++ b/ytLive.Tests/SceneOrderTests.cs @@ -0,0 +1,89 @@ +using System; +using System.IO; +using System.Linq; +using Microsoft.Data.Sqlite; +using Xunit; +using ytLive.ViewModels; + +namespace ytLive.Tests; + +/// +/// Scene-strip ordering vs F-keys (creator's report 2026-10-10). The five +/// canonical scenes are loaded ORDER BY SortOrder, and a session can +/// persist Chat before BRB, so the thumbnail strip renders in the wrong order +/// while each tab's F-key label is name-derived — "the ordering of the 5 +/// screens and their corresponding F-key is out of sequence". Fix: the VM +/// reorders Scenes to the canonical F1-F5 order (Starting, Live, BRB, Chat, +/// Ending) at load; the next save re-stamps SortOrder to the corrected order. +/// +[Collection("RealApp")] +public sealed class SceneOrderTests +{ + private readonly RealAppHost _app; + + public SceneOrderTests(RealAppHost app) => _app = app; + + [Fact] + public void Scenes_Load_Into_Canonical_FKey_Order_Whatever_The_SortOrder_Rows() + { + _app.Run(() => + { + using var layout = new TempSwappedOrderLayout(); + + var vm = new MainViewModel(); + + var names = vm.Scenes.Select(s => s.Name).ToArray(); + Assert.Equal(new[] { "Starting", "Live", "BRB", "Chat", "Ending" }, names); + + var keys = vm.Scenes.Select(s => s.HotkeyLabel).ToArray(); + Assert.Equal(new[] { "F1", "F2", "F3", "F4", "F5" }, keys); + + // The heal persists: a save re-stamps SortOrder, so a fresh load (the + // thumbnail strip's rendering source) keeps the canonical order. + vm.SaveLayoutNow(); + using var reloaded = new Services.LayoutStore(layout.DbPath); + Assert.Equal(new[] { "Starting", "Live", "BRB", "Chat", "Ending" }, + reloaded.Load().Select(s => s.Name).ToArray()); + }); + } + + /// Creates a DB whose five scenes are persisted with Chat BEFORE + /// BRB — the exact stale state found on the creator's box. + private sealed class TempSwappedOrderLayout : IDisposable + { + public string DbPath { get; } + private readonly string _path; + + public TempSwappedOrderLayout() + { + _path = Path.Combine(Path.GetTempPath(), $"ytLive-sceneorder-{Guid.NewGuid():N}.db"); + DbPath = _path; + using (var schema = new Services.LayoutStore(_path)) { } + SqliteConnection.ClearAllPools(); + MainViewModel.LayoutPathOverride = _path; + + using var connection = new SqliteConnection($"Data Source={_path}"); + connection.Open(); + using (var cmd = connection.CreateCommand()) + { + cmd.CommandText = """ + INSERT INTO Scene (Id, Name, IsHidden, IsChatScene, HasBackground, SortOrder) + VALUES + ('scene-starting', 'Starting', 0, 0, 1, 0), + ('scene-live', 'Live', 0, 0, 1, 1), + ('scene-chat', 'Chat', 0, 1, 1, 2), + ('scene-brb', 'BRB', 0, 0, 1, 3), + ('scene-ending', 'Ending', 0, 0, 1, 4); + """; + cmd.ExecuteNonQuery(); + } + } + + public void Dispose() + { + MainViewModel.LayoutPathOverride = null; + SqliteConnection.ClearAllPools(); + try { File.Delete(_path); } catch { /* best-effort cleanup */ } + } + } +} \ No newline at end of file