feat(ui): wire DI bootstrap in App.xaml.cs and add Windows solution filter
Some checks failed
CI / build-and-test (push) Failing after 36s

This commit is contained in:
Zac Gaetano 2026-05-07 15:41:58 +00:00
parent d64b110550
commit e3321ff279
5 changed files with 151 additions and 17 deletions

13
TeamsISO.Windows.slnf Normal file
View file

@ -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"
]
}
}

View file

@ -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.

View file

@ -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.

View file

@ -1,6 +1,5 @@
<Application x:Class="TeamsISO.App.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml">
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Application.Resources/>
</Application>

View file

@ -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<App>();
// ---- Preflight: NDI runtime ----
try
{
_interop = new NdiInteropPInvoke(_loggerFactory.CreateLogger<NdiInteropPInvoke>());
}
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<ConfigStore>());
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);
}
}