From e3321ff2793346241d0756005a9378c04a153b91 Mon Sep 17 00:00:00 2001 From: Zac Gaetano Date: Thu, 7 May 2026 15:41:58 +0000 Subject: [PATCH] feat(ui): wire DI bootstrap in App.xaml.cs and add Windows solution filter --- TeamsISO.Windows.slnf | 13 +++++ docs/superpowers/plans/_NEXT.md | 15 +++-- docs/test-playbook.md | 37 +++++++++--- src/TeamsISO.App/App.xaml | 3 +- src/TeamsISO.App/App.xaml.cs | 100 ++++++++++++++++++++++++++++++++ 5 files changed, 151 insertions(+), 17 deletions(-) create mode 100644 TeamsISO.Windows.slnf diff --git a/TeamsISO.Windows.slnf b/TeamsISO.Windows.slnf new file mode 100644 index 0000000..7756ab0 --- /dev/null +++ b/TeamsISO.Windows.slnf @@ -0,0 +1,13 @@ +{ + "solution": { + "path": "TeamsISO.sln", + "projects": [ + "src/TeamsISO.Engine/TeamsISO.Engine.csproj", + "src/TeamsISO.Engine.NdiInterop/TeamsISO.Engine.NdiInterop.csproj", + "src/TeamsISO.Console/TeamsISO.Console.csproj", + "src/TeamsISO.App/TeamsISO.App.csproj", + "src/tests/TeamsISO.Engine.Tests/TeamsISO.Engine.Tests.csproj", + "src/tests/TeamsISO.Engine.IntegrationTests/TeamsISO.Engine.IntegrationTests.csproj" + ] + } +} diff --git a/docs/superpowers/plans/_NEXT.md b/docs/superpowers/plans/_NEXT.md index f857104..f09d0fe 100644 --- a/docs/superpowers/plans/_NEXT.md +++ b/docs/superpowers/plans/_NEXT.md @@ -2,12 +2,15 @@ ## Completed phases -- **Phase A — Engine Foundation** (tag: `phase-a-complete`) — domain model, parsers, participant tracker, frame processor, config persistence, fakes, CI with 80% coverage gate. -- **Phase B-1 — Pipeline Orchestration** (tag: `phase-b-1-complete`) — `NdiReceiver`, `NdiSender`, `ExponentialBackoff`, `NdiRuntimeProbe`, `IsoPipeline` (with restart supervisor), `IsoController`. All testable on Linux. -- **Phase B-2 — Real NDI Interop** (tag: `phase-b-2-complete`) — production `NdiInteropPInvoke` against NDI 6 SDK, managed BGRA scaler with aspect modes, `TeamsISO.Console` headless smoke runner, `NdiVersion` constants. **Compiles on Linux; runs only on Windows with the NDI Runtime installed.** +- **Phase A — Engine Foundation** (tag: `phase-a-complete`) — domain model, parsers, participant tracker, frame processor, config, fakes, CI gate. +- **Phase B-1 — Pipeline Orchestration** (tag: `phase-b-1-complete`) — NdiReceiver, NdiSender, ExponentialBackoff, NdiRuntimeProbe, IsoPipeline supervisor, IsoController. +- **Phase B-2 — Real NDI Interop** (tag: `phase-b-2-complete`) — `NdiInteropPInvoke` against NDI 6 SDK, managed BGRA scaler, `TeamsISO.Console` headless smoke runner, `NdiVersion` constants. +- **Phase C — WPF UI** (tag: `phase-c-complete`) — MVVM helpers, ParticipantViewModel, GlobalSettingsViewModel, AlertBannerViewModel, MainViewModel, MainWindow XAML with participants DataGrid + settings sidebar + alert banner, App.xaml DI bootstrap. -## Next +## Next (Windows-only) -1. **Phase C — UI & Packaging** (Windows) — WPF MVVM app on top of `IIsoController`. Participant list (DataGrid bound to `Participants` observable), global settings view (framerate, resolution, aspect, audio mode), engine alert banner, system health indicators. WiX MSI installer, release pipeline on tag, About dialog. +1. **First end-to-end validation on Windows** — install NDI Runtime, clone the repo, build the full solution, run the integration tests against an NDI Test Pattern source, run the WPF app and validate against a real Teams meeting. Fix any issues found. -2. **First Windows validation** — once on a Windows machine: install NDI Runtime, run `dotnet build`, run `dotnet test --filter "requires=ndi"` against an NDI Test Pattern source, run `TeamsISO.Console --enable-all` against a real Teams meeting. +2. **Phase D — WiX Installer & Release** (Windows) — WiX v5 MSI installer detecting NDI Runtime, release pipeline triggering on tag push, code-signing decision implemented. + +3. **Optional polish before v1.0** — system health indicators (CPU/GPU/network meters), per-stream framerate display, output thumbnail previews (deferred from v1.5 if useful), MaterialDesignThemes UI polish. diff --git a/docs/test-playbook.md b/docs/test-playbook.md index c741ccc..7f0a204 100644 --- a/docs/test-playbook.md +++ b/docs/test-playbook.md @@ -1,15 +1,34 @@ # TeamsISO Manual Test Playbook -This doc grows with each phase. Phase A is unit-test only — nothing to verify against live Teams yet. Phase B will fill in NDI runtime checks; Phase C will add the live-meeting end-to-end checklist. +## Phase A — Engine foundation (CI) -## Pre-checks (run before each release branch) - -- [ ] `dotnet build TeamsISO.sln` succeeds with zero warnings on Windows. -- [ ] `dotnet build TeamsISO.Linux.slnf` succeeds with zero warnings on Linux/macOS. -- [ ] `dotnet test TeamsISO.Linux.slnf --filter "Category!=ndi&requires!=ndi"` reports all unit tests passing. -- [ ] CI run on `main` is green. +- [ ] `dotnet build TeamsISO.Linux.slnf` succeeds with zero warnings. +- [ ] `dotnet test TeamsISO.Linux.slnf --filter "Category!=ndi&requires!=ndi"` passes. +- [ ] CI on Forgejo Actions is green at HEAD. - [ ] Code coverage on `TeamsISO.Engine` is ≥80%. -## Live-meeting checklist (Phase C) +## First Windows validation (after Phase B-2 ships) -(To be added.) +Prerequisite: Windows 10/11 + NDI Runtime installed (https://ndi.video/tools/) + .NET 8 SDK. + +- [ ] Clone the repo on the Windows machine: `git clone https://forge.wilddragon.net/zgaetano/teamsiso.git`. +- [ ] `dotnet build TeamsISO.sln --configuration Release` succeeds. +- [ ] `dotnet test --filter "requires=ndi"` passes against an NDI Test Pattern source (start the test pattern from the NDI Tools menu before running). +- [ ] Run `dotnet run --project src/TeamsISO.Console` — confirm the engine starts, version probe matches, and Ctrl+C exits cleanly. + +## Live-meeting validation (after Phase C ships) + +- [ ] Configure a Teams meeting with 3+ participants, with NDI broadcast enabled in Teams. +- [ ] `dotnet run --project src/TeamsISO.App` launches the WPF UI without an NDI runtime warning banner. +- [ ] Participants list populates within ~2 seconds of opening the app. +- [ ] Participant rename mid-meeting transfers the row's identity (the rename heuristic). +- [ ] Toggle ISO on for one participant. Confirm the named output appears in vMix / OBS / Studio Monitor on the same LAN. +- [ ] Change global framerate to 59.94 fps; click Apply. New ISOs honor the new rate. +- [ ] Disconnect one participant; confirm their ISO transitions to the no-signal slate within 2.5 s. +- [ ] Run for 30 minutes; check FramesDropped / FramesDuplicated counters in the engine log are reasonable. + +## Pre-release checklist + +- [ ] Legal review of NDI SDK License v5 complete (per spec §7.3). +- [ ] Code-signing decision confirmed (yes/no for v1.0). +- [ ] WiX installer produces a working MSI on a clean Windows machine. diff --git a/src/TeamsISO.App/App.xaml b/src/TeamsISO.App/App.xaml index e703f02..8be975d 100644 --- a/src/TeamsISO.App/App.xaml +++ b/src/TeamsISO.App/App.xaml @@ -1,6 +1,5 @@ + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> diff --git a/src/TeamsISO.App/App.xaml.cs b/src/TeamsISO.App/App.xaml.cs index 0b4da7d..0360e00 100644 --- a/src/TeamsISO.App/App.xaml.cs +++ b/src/TeamsISO.App/App.xaml.cs @@ -1,7 +1,107 @@ +using System.IO; using System.Windows; +using System.Windows.Threading; +using Microsoft.Extensions.Logging; +using TeamsISO.App.ViewModels; +using TeamsISO.Engine.Controller; +using TeamsISO.Engine.Interop; +using TeamsISO.Engine.Logging; +using TeamsISO.Engine.NdiInterop; +using TeamsISO.Engine.Persistence; +using TeamsISO.Engine.Pipeline; namespace TeamsISO.App; public partial class App : Application { + private ILoggerFactory? _loggerFactory; + private NdiInteropPInvoke? _interop; + private IsoController? _controller; + private MainViewModel? _viewModel; + + protected override async void OnStartup(StartupEventArgs e) + { + base.OnStartup(e); + + try + { + _loggerFactory = EngineLogging.CreateConsole(LogLevel.Information); + var logger = _loggerFactory.CreateLogger(); + + // ---- Preflight: NDI runtime ---- + try + { + _interop = new NdiInteropPInvoke(_loggerFactory.CreateLogger()); + } + catch (Exception ex) + { + MessageBox.Show( + "TeamsISO could not initialize the NDI runtime.\n\n" + + "Install the NDI Runtime from https://ndi.video/tools/ and try again.\n\n" + + "Details: " + ex.Message, + "TeamsISO — NDI runtime missing", + MessageBoxButton.OK, + MessageBoxImage.Error); + Shutdown(2); + return; + } + + // ---- Engine wiring ---- + var configPath = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), + "TeamsISO", "config.json"); + var configStore = new ConfigStore(configPath, _loggerFactory.CreateLogger()); + + var probe = new NdiRuntimeProbe(_interop, NdiVersion.ExpectedRuntimeVersionPrefix); + var scaler = new ManagedNearestNeighborFrameScaler(); + + var loggerFactoryRef = _loggerFactory; + var interopRef = _interop; + IsoPipeline PipelineFactory(IsoPipelineConfig config) + { + var clock = new PeriodicTimerFrameClock(config.Settings.FramerateHz); + return new IsoPipeline( + config, interopRef, scaler, clock, + ExponentialBackoff.Default, + (delay, ct) => Task.Delay(delay, ct), + loggerFactoryRef); + } + + _controller = new IsoController( + _interop, PipelineFactory, configStore, probe, _loggerFactory); + + _viewModel = new MainViewModel(_controller, Dispatcher); + var window = new MainWindow(_viewModel); + window.Show(); + MainWindow = window; + + await _viewModel.InitializeAsync(CancellationToken.None); + } + catch (Exception ex) + { + MessageBox.Show( + "TeamsISO failed to start.\n\nDetails: " + ex, + "TeamsISO — startup error", + MessageBoxButton.OK, + MessageBoxImage.Error); + Shutdown(1); + } + } + + protected override async void OnExit(ExitEventArgs e) + { + try + { + _viewModel?.Dispose(); + if (_controller is not null) + await _controller.DisposeAsync(); + _interop?.Dispose(); + _loggerFactory?.Dispose(); + } + catch + { + // Best-effort shutdown + } + base.OnExit(e); + } }