From ff1c1965e061c0103df678fae869cda8a8b91ed6 Mon Sep 17 00:00:00 2001 From: gramps Date: Tue, 10 Nov 2026 17:56:24 -0800 Subject: [PATCH] fix(37): preserve camera choice when restoring default layouts Build-mismatch restoration now keeps the currently selected webcam identity, restores default scene placements against that camera, and leaves cameras absent when none is selected. The integration regression test pins the creator's camera choice and restored default geometry. 612 tests green, clean build with 0 warnings. --- Services/LayoutStore.Snapshots.cs | 10 +++++- ytLive.Tests/LayoutStoreSnapshotTests.cs | 41 ++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/Services/LayoutStore.Snapshots.cs b/Services/LayoutStore.Snapshots.cs index 403b5bf..d1d82ec 100644 --- a/Services/LayoutStore.Snapshots.cs +++ b/Services/LayoutStore.Snapshots.cs @@ -246,10 +246,12 @@ public partial class LayoutStore : IDisposable private void RestoreDefaults(string buildId) { var columnsByTable = SnapshotTables.ToDictionary(table => table, ReadColumnNames); + var currentWebcamId = GetCurrentWebcamId(); using var tx = _connection.BeginTransaction(); foreach (var table in SnapshotTables.Reverse()) { + if (table == "Webcam") continue; using var delete = _connection.CreateCommand(); delete.Transaction = tx; delete.CommandText = $"DELETE FROM {table};"; @@ -258,10 +260,16 @@ public partial class LayoutStore : IDisposable foreach (var table in SnapshotTables) { + if (table == "Webcam" || (table == "WebcamSceneConfig" && currentWebcamId == null)) continue; var columns = string.Join(", ", columnsByTable[table].Select(QuoteIdentifier)); + var values = string.Join(", ", columnsByTable[table].Select(column => + column == "WebcamId" && table == "WebcamSceneConfig" + ? "$webcamId" + : QuoteIdentifier(column))); using var copy = _connection.CreateCommand(); copy.Transaction = tx; - copy.CommandText = $"INSERT INTO {table} ({columns}) SELECT {columns} FROM Default{table};"; + copy.CommandText = $"INSERT INTO {table} ({columns}) SELECT {values} FROM Default{table};"; + if (table == "WebcamSceneConfig") copy.Parameters.AddWithValue("$webcamId", currentWebcamId!); copy.ExecuteNonQuery(); } diff --git a/ytLive.Tests/LayoutStoreSnapshotTests.cs b/ytLive.Tests/LayoutStoreSnapshotTests.cs index 0969f5d..344f4e7 100644 --- a/ytLive.Tests/LayoutStoreSnapshotTests.cs +++ b/ytLive.Tests/LayoutStoreSnapshotTests.cs @@ -116,6 +116,39 @@ public sealed class LayoutStoreSnapshotTests } } + [Fact] + public void DifferentBuild_RestoresWebcamPlacement_WithoutReplacingChosenCamera() + { + var path = Path.Combine(Path.GetTempPath(), $"ytLlive-layout-snapshot-{Guid.NewGuid():N}.db"); + var sceneId = Guid.NewGuid().ToString(); + var defaultWebcam = new Webcam { Id = "factory-webcam", DeviceId = "factory-device", Name = "Factory camera" }; + var chosenWebcam = new Webcam { Id = "chosen-webcam", DeviceId = "chosen-device", Name = "Chosen camera" }; + try + { + using (var store = new LayoutStore(path)) + { + store.Save(new[] { MakeWebcamScene(sceneId, defaultWebcam.Id, 20) }, defaultWebcam, null); + store.PrepareLayoutSnapshots("build-a"); + store.Save(new[] { MakeWebcamScene(sceneId, chosenWebcam.Id, 200) }, chosenWebcam, null); + } + + using (var reopened = new LayoutStore(path)) + { + reopened.PrepareLayoutSnapshots("build-b"); + var restored = Assert.Single(reopened.Load()); + var config = Assert.Single(restored.Elements.OfType()); + Assert.Equal(chosenWebcam.DeviceId, reopened.Webcam?.DeviceId); + Assert.Equal(chosenWebcam.Id, config.WebcamId); + Assert.Equal(20, config.X); + } + } + finally + { + SqliteConnection.ClearAllPools(); + try { File.Delete(path); } catch { /* best-effort cleanup */ } + } + } + private static Scene MakeScene(string sourceName) => MakeScene(SceneCatalog.Starting, sourceName); @@ -134,6 +167,14 @@ public sealed class LayoutStoreSnapshotTests Elements = { new Source { Name = sourceName, Type = SourceType.TextOverlay } }, }; + private static Scene MakeWebcamScene(string id, string webcamId, double x) + => new() + { + Id = id, + Name = SceneCatalog.Live, + Elements = { new WebcamSceneConfig { WebcamId = webcamId, Name = "Webcam", X = x, Y = 20, Width = 320, Height = 180 } }, + }; + private static SqliteConnection GetConnection(string path) { var connection = new SqliteConnection($"Data Source={path}");