Two new docs to land alongside the in-flight WinUI 3 work: * docs/superpowers/plans/2026-05-12-winui3-migration.md Full nine-phase migration plan. Locks the architectural decisions (WindowsAppSDK 1.6 LTS, unpackaged, win-x64 RID, custom Main with explicit Bootstrap, CommunityToolkit DataGrid 7.1.2, AppWindow title-bar API). Tracks what's done (Phase 1 + 2: scaffold and MainWindow shell), what's blocked (Phase 3: activation failure), and what's next (Phase 4-9). Risk register flags fallback paths. * docs/superpowers/work-log-2026-05-12.md Operator-readable summary of overnight progress. Leads with the pull-and-push reminder (forgejo credentials expired so commits are local-only until Zac authenticates and pushes manually), names the activation blocker with the diagnostic evidence captured, and suggests the first session tomorrow morning. Documents what was deliberately NOT touched (WPF host, Teams orchestration, view-model wiring) so the running build is unambiguously safe.
199 lines
9.7 KiB
Markdown
199 lines
9.7 KiB
Markdown
# 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)
|
||
|
||
- [x] `src/TeamsISO.App.WinUI/` project created with WindowsAppSDK 1.6
|
||
- [x] `Themes/Tokens.xaml` with Dark + Light ThemeDictionaries
|
||
- [x] `Themes/Controls.xaml` with Button hierarchy + typographic ramp
|
||
- [x] `App.xaml` + `App.xaml.cs` minimal startup
|
||
- [x] `Program.cs` custom Main with Bootstrap.TryInitialize
|
||
- [x] Assets copied (Inter.ttf, JetBrainsMono.ttf, dragon-mark.png, icon)
|
||
- [x] Solution updated (.sln + .slnf paths backslash-normalized)
|
||
- [x] `dotnet build TeamsISO.Windows.slnf -c Debug` is clean
|
||
|
||
### Phase 2 — MainWindow shell (done)
|
||
|
||
- [x] 64px left rail with brand mark + nav buttons + status puck
|
||
- [x] 44px custom title bar with absorbed live pills + theme toggle
|
||
- [x] Section header (Participants count + filter + actions + primary)
|
||
- [x] Participants list (ItemsRepeater + DataTemplate, mock data)
|
||
- [x] Conditional in-call control bar
|
||
- [x] Slim status bar at bottom
|
||
- [x] 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 |
|
||
| `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)
|