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);
+ }
}