Scene strip now renders in canonical F-key order regardless of persisted SortOrder. The strip loads Scenes ORDER BY SortOrder while each tab's F-key label is name-derived, so a session that persisted Chat before BRB (found on the creator's DB: SortOrder 2/3) showed tabs 'Chat F4' then 'BRB F3'. MainViewModel.LoadLayout now calls the new ReorderScenesToCanonical() - the five canonical scenes reorder to Starting, Live, BRB, Chat, Ending (F1-F5, stable among renamed non-canonical scenes), and the next auto-save re-stamps SortOrder so the heal persists. One integration test (SceneOrderTests) seeds the swapped-order DB and asserts loaded order, F1-F5 labels, and that a save persists the corrected ordering. 423 pass, 0 build warnings. (Unit: scene order)

This commit is contained in:
2026-10-10 12:09:07 -07:00
parent 63bf8444bd
commit 06f88f4142
5 changed files with 162 additions and 42 deletions
+32 -41
View File
@@ -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 ## At a glance
- **Branch** main (no feature branch). Pushed through `5685c5d`; everything after is local, **not - **Branch** main (no feature branch). Pushed through `5685c5d`; 18 commits local, **not pushed**
pushed** (push at the creator's call). (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, - Desktop-lock fix committed as `63bf844`; **scene-order fix below is uncommitted**.
(3) the creator's pinned-chat unit. Do NOT mix them in one commit.
## Branch ## Branch
- main (no feature branch). Pushed through `5685c5d` (TRAX toggle); everything after is local, - main (no feature branch). Pushed through `5685c5d` (TRAX toggle); everything after is local,
**not pushed** (push at the creator's call). **not pushed** (push at the creator's call).
## New work unit (this handoff) — READY TO VERIFY, do not commit yet ## 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` + - **Fix** for the scene-strip / F-key ordering report — `ViewModels/MainViewModel.Scenes.cs` new
`ViewModels/MainViewModel.Background.cs` (`ShowActivated = !FullscreenGameRunningNow`, see `ReorderScenesToCanonical()` (canonical F1-F5 order at load, renamed scenes after; next save
`bugs.md` entry 2026-10-10). Scope-locked files: `MainWindow.xaml.cs`, re-stamps SortOrder) called from `MainViewModel.cs:LoadLayout`; `ytLive.Tests/SceneOrderTests.cs`
`ViewModels/MainViewModel.Background.cs`, `ViewModels/MainViewModel.cs` (detector seam line), (new, seeds a Chat-before-BRB DB and asserts order + F1-F5 labels + persisted SortOrder).
`ytLive.Tests/DesktopLockTests.cs` (new), `bugs.md`, this file. See `bugs.md` entry 2026-10-10. Scope-locked files: `ViewModels/MainViewModel.Scenes.cs`,
- **Regression tests:** `DesktopLockTests` (3 tests) drive the rule through the NEW `ViewModels/MainViewModel.cs` (1 call line), `ytLive.Tests/SceneOrderTests.cs`, `bugs.md`,
`FullScreenDetectorOverride` test seam (the same seam pattern as `WindowEnumeratorOverride`) — `HANDOFF.md`.
no real Win32 foreground state is touched: full-screen game at launch ⇒ `ShowActivated=false`; - Full suite **423 passed / 0 failed / 0 warnings**. Root cause was the creator's own DB (Chat
normal launch ⇒ still `true`; `FullscreenGameRunningNow` reflects the detector. Full suite SortOrder 2, BRB 3); the fix heals it on next launch regardless of DB history.
**422 passed / 0 failed / 0 warnings**. Real-app reproduction still deferred to the creator. - **To commit later (this unit only):** the files listed above. Then the working tree is back to
- **To commit later (this unit only):** the two code files + the two memory files listed above. just the creator's pinned-chat unit (NOT mine to commit).
- 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.
## Dirty files — split by ownership ## Desktop-lock unit (COMMITTED 63bf844 — desktop "lock" fix)
- **This work unit (COMMIT these):** `Services/BackgroundEffects/MaskSmoother.cs` (new), the smoother - `ShowActivated = !FullscreenGameRunningNow` + `FullScreenDetectorOverride` seam + `DesktopLockTests`
wiring in `Services/CameraManager.cs`, `ytLive.Tests/CameraManagerTests.cs` (new integration test), (3 tests). Committed 2026-10-10. Creator still to verify in-game (launch while an
and the same-commit memory updates (`Services/index.md`, `ai.md`, `TASKS/task-51-webcam-bg-effects.md`, exclusive-fullscreen game owns the foreground — the game must stay live).
`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`, ## 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 `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 `Controls/LeftPanel.xaml`**), plus the creator's untracked `Assets/llamacasty*` art.
stayed unstaged/untouched), plus the creator's untracked `Assets/llamacasty*` art. - ⚠️ Pre-commit (scene-order unit): `scripts/verify.sh` runs `scripts/scope-check.sh` against ALL
- ⚠️ Pre-commit: `scripts/verify.sh` runs `scripts/scope-check.sh` against ALL working-tree changes — working-tree changes — the not-mine pinned-chat + art files WILL flag. Pass them as declared-known.
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.
## 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 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))`). ONE worker (`CameraManager.RunEffectWorker` → `session.Smoother.Smooth(TrySegment(source))`).
State on `CameraSession`, NOT the segmenter (shared app-wide, contract-pinned to raw output). Null 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. the 0.5 rounding (3 box passes dilute a spike) — use a step edge, not a spike, to measure blur.
## Next step ## Next step
- Creator verifies the desktop-lock fix: while THE DIVISION 2 (or any exclusive-fullscreen game) is the - Creator launches the app: the scene strip must show Starting/Live/BRB/Chat/Ending in order with
foreground window, launch the app — the game must stay live and the app must not yank focus. Also F1-F5 correct (the creator's DB had Chat before BRB; the load-time reorder heals it and the next
confirm a normal launch is unchanged. Then I can commit the unit and run the full suite when the save re-stamps SortOrder). Also still to verify when convenient: the desktop-lock fix while an
mouse is free. exclusive-fullscreen game owns the foreground. Then I can commit the scene-order unit.
## Tests ## Tests
- Full suite **422 passed / 0 failed / 0 skipped / 0 warnings** (2026-10-10; includes the 3 new - Full suite **423 passed / 0 failed / 0 skipped / 0 warnings** (2026-10-10; includes the new
`DesktopLockTests`). Known env flakes remain `Mix_HonorsProviderGains…` (fails while system audio `SceneOrderTests`). Known env flakes remain `Mix_HonorsProviderGains…` (fails while system audio
plays) and `RealMouseDrag…` (needs an idle desktop). plays) and `RealMouseDrag…` (needs an idle desktop).
## Also still open ## Also still open
@@ -78,8 +74,3 @@
- Blur-heaviness = fixed (above). Yellow-border regression = stale thread. - Blur-heaviness = fixed (above). Yellow-border regression = stale thread.
- ⛔ Sudo password rotation pending (user; then update both password lines in `CREDENTIALS.md`). - ⛔ 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. - 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).
+26
View File
@@ -196,6 +196,32 @@ public partial class MainViewModel
public IEnumerable<string> MissingScenes public IEnumerable<string> MissingScenes
=> SceneCatalog.All.Where(canonical => Scenes.All(s => !SceneCatalog.Is(s.Name, canonical))); => SceneCatalog.All.Where(canonical => Scenes.All(s => !SceneCatalog.Is(s.Name, canonical)));
/// <summary>
/// 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 <see cref="Enumerable.OrderBy"/>, so one pass; the next
/// auto-save re-stamps each SortOrder to its corrected index.
/// </summary>
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;
}
/// <summary>Only show the "+" button when one of the five canonical scenes is missing.</summary> /// <summary>Only show the "+" button when one of the five canonical scenes is missing.</summary>
public bool ShowAddScene => MissingScenes.Any(); public bool ShowAddScene => MissingScenes.Any();
+1
View File
@@ -411,6 +411,7 @@ public partial class MainViewModel : ViewModelBase
foreach (var name in SceneCatalog.All) foreach (var name in SceneCatalog.All)
AddScene(name); AddScene(name);
} }
ReorderScenesToCanonical();
HealBackgrounds(); HealBackgrounds();
HealZoomLayers(); HealZoomLayers();
RefreshHotkeyLabels(); RefreshHotkeyLabels();
+13
View File
@@ -1,5 +1,18 @@
# Bugs # 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) ## 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 — Report: once the app is started, the desktop is "locked" — the mouse can no longer interact with it —
which made streaming live gameplay impossible. which made streaming live gameplay impossible.
+89
View File
@@ -0,0 +1,89 @@
using System;
using System.IO;
using System.Linq;
using Microsoft.Data.Sqlite;
using Xunit;
using ytLive.ViewModels;
namespace ytLive.Tests;
/// <summary>
/// Scene-strip ordering vs F-keys (creator's report 2026-10-10). The five
/// canonical scenes are loaded <c>ORDER BY SortOrder</c>, 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.
/// </summary>
[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());
});
}
/// <summary>Creates a DB whose five scenes are persisted with Chat BEFORE
/// BRB — the exact stale state found on the creator's box.</summary>
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 */ }
}
}
}