- Fix TeamsISO.Windows.slnf — drop the dangling src/TeamsISO.App.WinUI/TeamsISO.App.WinUI.csproj entry whose project doesn't exist in the .sln (broke the build on main). - Archive the abandoned WinUI 3 artifacts under docs/archive/: * 2026-05-12-winui3-migration.md (the nine-phase migration plan) * TeamsISO.App.WinUI.Probe/ (the bootstrap diagnostic console) * work-log-2026-05-12-winui3.md (the overnight session log) - README — drop the "in-flight WinUI 3 replatform" status block; state that the v2 redesign landed in WPF and link the shape brief. Keyboard shortcuts table picks up Ctrl+K, Ctrl+T, and the digit hotkeys that already shipped. - CHANGELOG — replace the WinUI-3-flavoured "Ground-up GUI redesign" block with a v2 Studio Terminal entry that names Task 39 + Task 40 as landed. De-dupe the May 2026 batch: the second "Quick-join Teams meeting from URL", "IN-CALL bar surfaces Teams meeting state", and "Auto-launch Teams + auto-hide windows" bullets were verbatim repeats of earlier entries; kept the first occurrence. - NEXT_STEPS.md — rewrite to reflect that Task 39 (participants table v2) and Task 40 (Ctrl+K palette) both shipped; v1.0 cut is now gated only on MSI signing + real-meeting smoke pass. - DESIGN.md — small WPF-isms: WinUI 3 composition layer → WPF's; Segoe Fluent Icons phrased without the "WinUI 3's bundled" qualifier; migration boundary rephrased to "rewrites MainWindow.xaml + Themes/*" instead of "everything in Views/". - .gitignore — ignore the .claude/ session metadata dir so it doesn't show up as untracked on every dev checkout. Build + tests verified before commit: 0 errors, 0 warnings; 160 tests pass (56 App + 104 Engine, filter Category!=ndi&requires!=ndi). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
9.7 KiB
WinUI 3 migration plan
Started: 2026-05-12 (overnight) Status: in flight — scaffold + redesigned MainWindow + theme system landed, runtime activation blocked, view-model wiring not yet started.
The full plan for replatforming TeamsISO from WPF / .NET 8 to WinUI 3 / Windows App SDK 1.6 LTS. The redesigned UI per the approved shape brief (PRODUCT.md, DESIGN.md, the 2026-05-12 chat transcript) lands as the new TeamsISO.App.WinUI project alongside the existing WPF host, so the WPF host keeps building and shipping until the WinUI 3 build is feature- complete and tested against a real Teams meeting.
Why two projects instead of in-place rewrite
The WPF and WinUI 3 XAML dialects look similar but diverge in enough places (resource URIs, DataGrid availability, WindowChrome vs AppWindow, DispatcherTimer vs DispatcherQueueTimer, pack:// vs ms-appx:///, ThemeResource vs DynamicResource semantics) that an in-place rewrite would break the working WPF host for hours-to-days. Coexisting both projects means:
dotnet build TeamsISO.Windows.slnfkeeps producing a working WPF .exe throughout the migration.- Each WinUI 3 view can be migrated and verified independently.
- The engine layer (TeamsISO.Engine, TeamsISO.Engine.NdiInterop) and the view-models (TeamsISO.App/ViewModels/) are shared via ProjectReference. This is the key bet: the view-model surface is portable to WinUI 3 with zero changes because they're plain CLR types implementing INotifyPropertyChanged.
- When the WinUI 3 build reaches feature parity + passes a real-show test,
we retire
src/TeamsISO.Appand the WinUI 3 project becomes the only shipping host.
Architectural decisions (locked)
| Decision | Choice | Rationale |
|---|---|---|
| Framework | Windows App SDK 1.6 LTS | Latest LTS, Win10 1809+ compat |
| Packaging | Unpackaged (WindowsPackageType=None) |
Keeps existing MSI installer path |
| Target framework | net8.0-windows10.0.19041.0 |
WindowsAppSDK 1.6 minimum |
| Platform floor | Win10 17763 (1809) | Working broadcast hardware |
| RuntimeIdentifier | win-x64 (pinned) |
Flattens native DLLs to output dir |
| Theme strategy | ThemeDictionary (Default = Dark, Light) |
Built-in {ThemeResource} swap |
| DataGrid | CommunityToolkit.WinUI.UI.Controls.DataGrid 7.1.2 |
Only maintained free option |
| View-model | Reuse from TeamsISO.App via ProjectReference | Zero porting cost |
| Window chrome | AppWindow.TitleBar.ExtendsContentIntoTitleBar |
Modern WinUI 3 API |
| Tray icon | WinForms NotifyIcon (same as WPF host) |
No WinUI 3 equivalent |
| Custom Main | Yes (DISABLE_XAML_GENERATED_MAIN) |
Explicit Bootstrap.TryInitialize |
Phases
Phase 1 — Scaffold (done)
src/TeamsISO.App.WinUI/project created with WindowsAppSDK 1.6Themes/Tokens.xamlwith Dark + Light ThemeDictionariesThemes/Controls.xamlwith Button hierarchy + typographic rampApp.xaml+App.xaml.csminimal startupProgram.cscustom Main with Bootstrap.TryInitialize- Assets copied (Inter.ttf, JetBrainsMono.ttf, dragon-mark.png, icon)
- Solution updated (.sln + .slnf paths backslash-normalized)
dotnet build TeamsISO.Windows.slnf -c Debugis clean
Phase 2 — MainWindow shell (done)
- 64px left rail with brand mark + nav buttons + status puck
- 44px custom title bar with absorbed live pills + theme toggle
- Section header (Participants count + filter + actions + primary)
- Participants list (ItemsRepeater + DataTemplate, mock data)
- Conditional in-call control bar
- Slim status bar at bottom
- Theme toggle wires Window.Content.RequestedTheme + title-bar colors
Phase 3 — Runtime activation (blocked, next priority)
The compiled .exe shows "TeamsISO.exe - This application could not be started" before Main() runs. COREHOST_TRACE confirms .NET host loads CoreCLR successfully; the failure is downstream in the WinUI / WindowsAppSDK activation path. Suspected causes (in priority order):
- Missing manifest: WinUI 3 unpackaged needs a specific COM activation
manifest. Our custom
app.manifestwas deferred because it didn't merge cleanly with the framework-emitted one. Reintroduce with properuap:VisualElements. - Microsoft.WindowsDesktop.App framework reference: runtimeconfig.json
includes
Microsoft.WindowsDesktop.App 8.0.0, which WinUI 3 doesn't want. The .NET SDK adds it implicitly from the-windowstarget framework moniker. Try<EnableMsixTooling>true</EnableMsixTooling>- remove from frameworks list.
- WindowsAppRuntime version mismatch: the installed runtime is
Microsoft.WindowsAppRuntime.1.6 (6000.519.329.0). Bootstrap.TryInitialize should accept any 1.6.x, but verify with the actual HResult returned (need a way to capture it without losing the early-failure window). - Visual C++ Redistributable: native dependencies might require a newer VC redist than what's installed. Check WindowsAppSDK 1.6's redist requirements.
Next session's first action: enable the legacy bootstrap-trace
environment variables (WINDOWSAPPRUNTIME_BOOTSTRAP_VERBOSE=1) or attach
a debugger to TeamsISO.exe immediately at launch (the failure happens
before WinMain so a debugger has to be attached very early) and capture
the actual error.
Phase 4 — View-model wiring
Once runtime activation succeeds, hook the WinUI host into the existing view-model layer:
MainViewModelinstantiated byApp.OnLaunched(mirror WPF App.xaml.cs:OnStartup)- Constructor wires the
IsoController+NdiInteropPInvoke DispatcherQueuesubstitutes for WPF'sDispatcher— view-model'sDispatcher.InvokeAsynccalls need adapting toDispatcherQueue.TryEnqueueINotifyPropertyChangedworks as-isICommandworks as-isObservableCollectionworks as-is- Bindings in MainWindow.xaml updated from {Binding ...} to {x:Bind ...} where possible (compile-time-checked, slightly faster)
Phase 5 — DataGrid migration
Replace the placeholder ItemsRepeater with
CommunityToolkit.WinUI.UI.Controls.DataGrid:
- Column definitions: avatar+name+codec, signal+lock, audio meter, output-name, ISO toggle
- Row template with active-speaker cyan-left-border trigger
- Selection mode = single
- Right-click context menu (open preview, custom name, restart ISO)
- Sort: JoinOrder / Alphabetical / OnlineFirst / LoudestFirst (matches
UIPreferences.SortMode)
Phase 6 — Secondary windows
- Settings drawer (
SettingsDrawer.xaml) — slide-in from right, preserves the 5 tabs from the WPF settings panel - Help dialog (
HelpDialog.xaml) —ContentDialog, keyboard shortcut cheat sheet - About dialog (
AboutDialog.xaml) — version, logs path, update check - Onboarding (
OnboardingWindow.xaml) — first-launch only, three panes - Notes viewer (
NotesViewer.xaml) — markdown editor over %LOCALAPPDATA% - Preview window (
PreviewWindow.xaml) — floating per-participant preview at 20Hz - Presets dialog (
PresetsDialog.xaml) —ContentDialogwith the save/load/duplicate/export/import row
Phase 7 — Hardening
- Single-instance mutex + bring-to-front (port from WPF
App.xaml.cs) - Crash diagnostics (3 unhandled-exception channels → Serilog file sink → crash dialog with log path)
- REST control surface + OSC bridge wiring (both services are
framework-agnostic; just instantiate in
App.OnLaunched) - Tray icon (port
TrayIconHost.cs— WinForms.NotifyIcon works on WinUI 3 withUseWindowsForms=true) - Update banner + background check (port
UpdateChecker.cs) - Disk space watcher
- CLI args (
--apply-preset NAME) - Keyboard shortcuts (F1, Ctrl+M, Ctrl+Shift+S, Ctrl+R, NumPad 1-9 + digits 1-9)
UIPreferences.Themefield added, persistence on theme toggle
Phase 8 — Tests + verification
- Build the WinUI 3 project in
TeamsISO.App.Tests(currently targetsnet8.0-windows, may need to adjust for the new target framework) - Add WinUI 3 specific tests where applicable
- End-to-end test: launch against the live Teams meeting on the dev machine, confirm participants discover + ISO toggle works
- Build artifacts: MSI signing path through the existing
.forgejo/workflows/release.yml
Phase 9 — Retire WPF host
dotnet sln remove src/TeamsISO.App/TeamsISO.App.csproj- Delete
src/TeamsISO.App/directory - Update README.md and CHANGELOG.md
- Tag v1.0.0 (the original v1.0 cut moves to v0.9; v1.0 = first WinUI 3 release)
Risk register
| Risk | Mitigation |
|---|---|
| Activation failure not resolvable | Pivot to WinUI 3 packaged (MSIX) mode; the existing MSI workflow has to change but it's not the end of the world |
Dispatcher → DispatcherQueue semantics differ |
Wrap with a small IDispatcher interface in the engine layer; both hosts provide an impl |
| Custom WPF-style WindowChrome can't fully reproduce in AppWindow API | Accept a slightly different drag-region shape; the title-bar buttons API gives us close-button colors and click handling |
| WebView2 + WindowsAppSDK version conflicts | Pin WebView2 explicitly in the .csproj |
| CommunityToolkit DataGrid 7.x maintenance ending | Plan a fallback to WinUI.TableView 1.4.x as a contingency |
| Performance regression on the participants table (thumbnails at 20Hz × N rows) | Profile early; if needed, use Win2D for the audio meter and signal indicator |
What I'm NOT doing
- Replacing the engine layer
- Touching the NDI native interop
- Changing the control surface protocol (REST/WebSocket/OSC)
- Migrating tests right now (Phase 8)
- Adding new product features (anything not in the redesign brief stays for a follow-on release)