teamsiso/docs/archive/2026-05-12-winui3-migration.md
Zac Gaetano 37390026b3 chore(docs): reconcile to WPF-only after WinUI 3 was abandoned
- 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>
2026-05-15 19:16:20 -04:00

9.7 KiB
Raw Blame History

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:

  1. dotnet build TeamsISO.Windows.slnf keeps producing a working WPF .exe throughout the migration.
  2. Each WinUI 3 view can be migrated and verified independently.
  3. 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.
  4. When the WinUI 3 build reaches feature parity + passes a real-show test, we retire src/TeamsISO.App and 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.6
  • Themes/Tokens.xaml with Dark + Light ThemeDictionaries
  • Themes/Controls.xaml with Button hierarchy + typographic ramp
  • App.xaml + App.xaml.cs minimal startup
  • Program.cs custom 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 Debug is 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):

  1. Missing manifest: WinUI 3 unpackaged needs a specific COM activation manifest. Our custom app.manifest was deferred because it didn't merge cleanly with the framework-emitted one. Reintroduce with proper uap:VisualElements.
  2. 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 -windows target framework moniker. Try <EnableMsixTooling>true</EnableMsixTooling>
    • remove from frameworks list.
  3. 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).
  4. 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:

  • MainViewModel instantiated by App.OnLaunched (mirror WPF App.xaml.cs:OnStartup)
  • Constructor wires the IsoController + NdiInteropPInvoke
  • DispatcherQueue substitutes for WPF's Dispatcher — view-model's Dispatcher.InvokeAsync calls need adapting to DispatcherQueue.TryEnqueue
  • INotifyPropertyChanged works as-is
  • ICommand works as-is
  • ObservableCollection works 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) — ContentDialog with 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 with UseWindowsForms=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.Theme field added, persistence on theme toggle

Phase 8 — Tests + verification

  • Build the WinUI 3 project in TeamsISO.App.Tests (currently targets net8.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
DispatcherDispatcherQueue 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)