- 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>
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)
|