Services/UIPreferences.cs — mirror of the WPF host's UIPreferences,
sharing %LOCALAPPDATA%\TeamsISO\ui-prefs.json on disk. Adds a Theme
field ("System" / "Dark" / "Light") that the WPF host's UIPreferences
will pick up when its theme system lands (JSON deserialization is
forward-compatible — extra fields are ignored, missing fields fall
back to defaults).
ThemeManager hydration:
* Constructor reads UIPreferences.Theme on first .Current access.
* Defaults to "System" when the file is missing, the value is
invalid, or load throws (defensive — ThemeManager.Current is a
static singleton, a throw would break theme resolution app-wide).
ThemeManager.Set persistence:
* Calls UIPreferences.SetTheme(preference) which does a read-modify-
write of the JSON (so other fields aren't trampled).
* Persistence is best-effort wrapped in try/catch — disk full,
permission denied, etc. fall through and the in-memory state still
holds for the session.
End-to-end now: title-bar sun/moon toggle → ThemeManager.Toggle →
.Set("Dark"/"Light") → JSON write → next launch reads the preference
and applies before the first frame. Operator's theme choice survives
across launches and across host swaps once the WPF host learns the
field.
In-call control bar now drives the live Teams app via UIAutomation:
* Mute button → TeamsControlBridge.ToggleMute()
* Camera button → TeamsControlBridge.ToggleCamera()
* Share button → TeamsControlBridge.OpenShareTray()
* Leave button → TeamsControlBridge.LeaveCall()
Each button reports the result through the status bar (Invoked /
Teams-not-running / Control-not-visible / Invoke-failed).
Rail buttons also wired:
* Launch / surface Teams → TeamsLauncher.IsRunning()/TryLaunch()/ShowWindows()
* Hide / show Teams windows → TeamsLauncher.HideWindows()/ShowWindows()
with a _teamsHidden flag tracking the toggle state
The Marker button was already command-bound to MainViewModel.DropRecording
MarkerCommand (which fans out to IIsoController.AddRecordingMarker), so
the only thing that wasn't covered before is the Teams-side stuff.
Implementation notes:
* Services/TeamsControlBridge.cs and Services/TeamsLauncher.cs are
copied verbatim from src/TeamsISO.App/Services/ with only the
namespace adjusted (TeamsISO.App.Services → TeamsISO.App.WinUI.
Services). Neither file has WPF-specific dependencies — they use
System.Windows.Automation (UIAutomationClient) which works
identically across WPF and WinUI 3 builds. Duplication is
acceptable migration debt; the long-term plan is to lift these
into a shared TeamsISO.App.Shared library once both hosts
stabilize.
* DescribeBridgeResult maps the InvokeResult enum to operator-tone
status text so a failing mute reads "Mute failed — control not
visible (not in a call?)" instead of an opaque "ControlNotFound".
The in-call bar now does what the WPF host's in-call bar does, minus
the MUTED / CAM OFF state pills (those would need a 1Hz UIA poll of
the Teams call state — wire-up to come).
Two screenshots captured from the live TeamsISO.App.WinUI .exe at
1280×780, one per theme. Both prove the redesign renders end-to-end
on Windows 11 with WindowsAppSDK 1.8 and no view-model wiring yet:
* docs/preview/winui3-mainwindow-light.png — App.Current.RequestedTheme
set to Light via ThemeManager. Wild Dragon "W" mark renders as cyan
(#0E7C82) on cyan-muted (#E6F8F9) tile per the light-mode accent
split from DESIGN.md. All other rail icons render at FgSecondary
(#4A4B50) for AA contrast.
* docs/preview/winui3-mainwindow-dark.png — same render, dark theme.
Wild Dragon mark uses the airy #97EDF0 cyan on the deeper
cyan-muted (#1B3537) tile. Rail icons + section text at FgPrimary
(#F4F4F6).
ThemeManager default reverted to "System" (the screenshot for dark
mode was taken with the default temporarily set to "Dark", then
reverted before this commit). The light-mode screenshot is what runs
when the OS app-mode is light, which is what happened on this build
host tonight.
These are the artifacts to point at when stakeholders ask "what does
the redesign look like in practice?" — they are the WinUI 3 .exe, not
the HTML preview.
Builds out the secondary surfaces of the redesigned WinUI 3 host.
ThemeManager (Services/ThemeManager.cs)
Single-source-of-truth for the active theme. Holds the user preference
(System / Dark / Light), resolves it to ElementTheme at request, and
raises a Themed event when it changes so the MainWindow can push the
AppWindow title-bar button colors. Uses Windows.UI.ViewManagement
UISettings to follow the OS app-mode when preference is System.
Persistence to UIPreferences lands in the engine-wiring commit.
MainWindow theme wiring
Replaces the per-handler theme toggle with a ThemeManager subscription:
click the title-bar sun/moon -> Toggle() -> Themed event ->
ApplyResolvedTheme on the visual tree + the title-bar buttons. Glyph
cue: sun = "current is Light, click to Dark"; moon = "current is Dark,
click to Light." Initial state applied at construction so the first
frame matches the preference.
SettingsDrawer (Views/SettingsDrawer.xaml + .cs)
UserControl that slides in from the right over the participants table.
56px header, NavigationView with five tabs (Appearance, Routing,
Display, Control, Advanced), footer with Reset-to-defaults +
Apply/Close. Appearance tab has the theme tri-state picker (System /
Dark / Light radio group) and an "Accent peek" row showing the four
brand accents (cyan / coral / live / warn) as swatches so the
operator can verify Wild Dragon brand is respected on a light desk.
CloseRequested event signals the host to collapse the drawer.
HelpDialog (Views/HelpDialog.xaml + .cs)
ContentDialog with the keyboard shortcut cheat sheet, grouped by
category (Global / Participants / Look / Control surface). 540px max
height with scroll, mono-spaced shortcut labels at left, body text at
right. Replaces the WPF host's HelpWindow at parity.
AboutDialog (Views/AboutDialog.xaml + .cs)
ContentDialog with the Wild Dragon mark, version + host + engine +
brand info as label/value rows, and three quick action buttons
(open logs folder, open recordings, check for updates). Mirrors the
WPF host's AboutWindow.
OnboardingDialog (Views/OnboardingDialog.xaml + .cs)
Three numbered steps (Install NDI Runtime / Enable Teams NDI / Pick
transcoder topology), no carousel, operator-tone copy ("Don't show
this again" defaults checked). PrimaryButtonText "Get started",
SecondaryButtonText "Skip" so the dialog is skippable from the first
frame as the PRODUCT.md anti-references demand.
Build clean: dotnet build TeamsISO.App.WinUI -c Debug -> 0 / 0.
Next: wire the drawer's CloseRequested into MainWindow (so the settings
icon actually opens / collapses the drawer), then attack the runtime
activation blocker (Phase 3 of the migration plan).