teamsiso/src/TeamsISO.App.WinUI/Views/MainWindow.xaml.cs

457 lines
18 KiB
C#
Raw Normal View History

feat(winui3): redesigned MainWindow + custom title bar + theme toggle Lands the approved shape brief as the WinUI 3 MainWindow: * 64px left rail with brand mark, primary nav (participants), Teams launch / hide / settings buttons, and the engine-status puck at the bottom. All five rail buttons use Segoe Fluent Icons glyphs at a uniform 20px optical size; no more bespoke <Path Data> shapes with inconsistent stroke weights. * 44px custom title bar via ExtendsContentIntoTitleBar + SetTitleBar(AppTitleBar). The drag region absorbs the three live-state pills inline (session timer 'live * 00:14:32', REC count + elapsed, disk free) and a slim sun/moon theme-toggle button to the left of the system Min/Max/Close controls. System buttons inherit ButtonForeground Color etc. from AppWindow.TitleBar so they match palette in both themes. * Section header with 'Participants * count' display, filter input, Refresh + Presets (Secondary buttons), and 'Enable all online' as the single cyan Primary button - finally a real button hierarchy instead of seven indistinguishable ghost buttons. * Participants list rendered as ItemsRepeater + DataTemplate for now; the CommunityToolkit DataGrid migration follows in a separate commit. Row template at 64px height with: 3px cyan left border for active speaker, avatar with initials in cyan-muted circle, name + codec line, signal lock state with dot, audio meter via ProgressBar, output name in JetBrains Mono, ISO state pill (LIVE/OFF/ERROR) at right. * Conditional in-call control bar below the table: Mute / Camera / Share / Marker / Leave + overflow kebab. Muted state binds the destructive coral treatment to the Mute button; Leave is also destructive (coral border + text); everything else is Secondary. Tight 8px spacing keeps the bar dense without crowding. * Slim 32px status bar at the bottom: control-surface URL on the left (cyan dot indicator), keyboard-shortcut hints on the right in tertiary mono. Replaces the WPF host's six-column footer. Implementation notes: * MockParticipant model populates the table with representative data (Maya / Daniel / Aicha / Sam, one as active speaker) until the ParticipantViewModel binding migrates over from the WPF host. * Custom Program.cs takes ownership of Main from the XAML compiler (DISABLE_XAML_GENERATED_MAIN). Calls Bootstrap.TryInitialize(0x00010006) before Application.Start so the unpackaged .exe can locate the WindowsAppSDK 1.6 framework MSIX at launch. Shutdown is paired in a finally block. * Theme toggle in code-behind flips Window.Content.RequestedTheme between Dark and Light. {ThemeResource} bindings auto-swap across the visual tree; system title-bar buttons (outside the XAML tree) get color updates inline so they stay readable in both modes. * app.manifest deferred from build - the framework-emitted manifest covers DPI awareness and supportedOS GUIDs; reintroducing our own goes in the next commit alongside the bootstrapper hardening. Known issue: the unpackaged .exe currently fails to activate on this build host with 'this application could not be started' before Main runs. Build is clean; published output runs the same way. Diagnosing the activation failure is the next session's first task (likely the runtimeconfig.json including Microsoft.WindowsDesktop.App which WinUI 3 doesn't want, or a missing CRT redistributable). The WPF host remains the running build until that's resolved. dotnet build TeamsISO.Windows.slnf -c Debug: 0 warnings, 0 errors.
2026-05-13 00:03:12 -04:00
using Microsoft.UI;
using Microsoft.UI.Windowing;
feat(winui3): scaffold TeamsISO.App.WinUI alongside the WPF host First step of the WinUI 3 replatform per the approved redesign brief. The new project coexists with the existing src/TeamsISO.App (WPF) so the WPF host keeps building and shipping while the WinUI 3 redesign lands incrementally. Once the WinUI 3 build is feature-complete and tested against a real Teams meeting, the WPF project is retired. Scaffold contents: * src/TeamsISO.App.WinUI/TeamsISO.App.WinUI.csproj Windows App SDK 1.6 LTS (250602001), unpackaged mode (WindowsPackageType=None) so the existing MSI installer keeps working. Target framework net8.0-windows10.0.19041.0, min platform 10.0.17763.0 to preserve Win10 1809+ compatibility for working broadcast hardware. Pins WindowsSdkPackageVersion=10.0.19041.38 so .NET SDK 8.0.301 builds cleanly without an SDK upgrade on the build host. * src/TeamsISO.App.WinUI/app.manifest PerMonitorV2 DPI awareness + gdiScaling for crisp text on high-DPI broadcast monitors. asInvoker trust level (control surface :9755 and OSC :9000 bind to 127.0.0.1, no admin needed). * App.xaml + App.xaml.cs Minimal startup: brings up MainWindow. The full pipeline (NDI runtime preflight, IsoController wiring, single-instance mutex, REST + OSC bridge, tray icon, crash diagnostics, auto-update banner, onboarding) migrates in subsequent commits. * Themes/Tokens.xaml Wild Dragon design tokens as ThemeDictionary entries (Default = Dark, Light). Colors as Color resources, Brushes paired per theme so {ThemeResource} auto-swaps when RequestedTheme flips — no app restart, no flicker. Spacing/radii/typography tokens are theme-agnostic at the outer level. Light palette maintains brand recognition via cyan-tinted off-whites (#FAFAFB canvas, #F0F1F3 rail) rather than pure white, and splits cyan into accent.cyan.surface (#97EDF0, works in both modes because text on top is near-black) and accent.cyan.text (#97EDF0 dark / #0E7C82 light) so captions and inline labels keep AA contrast. * Themes/Controls.xaml Button hierarchy with real commitments: Primary (cyan fill, one per surface), Secondary (transparent bordered), Tertiary (text only), Destructive (coral border + text), Caption (titlebar), RailIcon. Typographic ramp (Display / Title / Heading / Body / Subtle / Caption / Mono) at the DESIGN.md 1.25 ratio. * CommunityToolkit.WinUI.UI.Controls.DataGrid 7.1.2 referenced for the participants table migration. (Toolkit 8.x dropped DataGrid; 7.x is the only currently-maintained free option for WinUI 3.) * Inter.ttf + JetBrainsMono.ttf + dragon-mark.png + teamsiso.ico copied from the WPF project's Assets/ so the WinUI 3 host is self-contained. * TeamsISO.sln + TeamsISO.Windows.slnf updated to include the new project. The .slnf paths switch to backslash form so MSBuild can match them against the .sln's canonical path representation. Verified: dotnet build TeamsISO.Windows.slnf -c Debug succeeds with 0 warnings and 0 errors for all 8 projects (WPF host, WinUI 3 host, engine, NDI interop, console, three test projects).
2026-05-12 23:52:35 -04:00
using Microsoft.UI.Xaml;
feat(winui3): ThemeManager service + Settings drawer + Help/About/Onboarding 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).
2026-05-13 00:13:58 -04:00
using TeamsISO.App.WinUI.Services;
feat(winui3): engine wired — discovers Teams participants live The WinUI 3 host now stands up the full engine pipeline on launch and discovers participants from the operator's live Teams meeting. Verified end-to-end against a real call: window opened, NDI runtime preflight passed, IsoController spun up, and Participants observable yielded three live entries ((Local), Active Speaker, Brendon Power) with their TEAMSISO_<name> output names in the redesigned shell. What this commit lands: ViewModels/ (slim ports of the WPF host's view-models — engine layer shared verbatim via ProjectReference): * ObservableObject.cs — INPC base, mirrors the WPF version * RelayCommand.cs — sync + typed + async variants; ICommand is the same shared type across both hosts (System.ObjectModel.dll) * ParticipantViewModel.cs — DisplayName / Initials / SourceCodec / IsoStateLabel / DisplayedAudioLevel / IsActiveSpeaker / IsOnline / OutputName + ToggleIsoCommand. Drops the WPF-specific thumbnail WriteableBitmap path, clipboard, PreviewWindow, snapshot encoder — those come back when the WinUI imaging pipeline is wired (Phase 5 of the migration plan). * MainViewModel.cs — subscribes to IIsoController.Participants on a DispatcherQueueSynchronizationContext, owns the ObservableCollection, runs a 1Hz DispatcherQueueTimer for stats + active-speaker highlight + session-elapsed text. Commands: EnableAllOnline, StopAllIsos, RefreshDiscovery, DropRecordingMarker, ToggleByIndex. App.xaml.cs: * OnLaunched brings up MainWindow first, then fires WireEngineAsync so the user sees the shell immediately while NDI preflight + engine setup proceed. * Full pipeline: EngineLogging → NdiInteropPInvoke (with friendly fallback message if the NDI runtime isn't installed) → ConfigStore at %APPDATA%\TeamsISO\config.json → NdiRuntimeProbe + scaler → IsoPipeline factory → IsoController → MainViewModel → MainWindow.AttachViewModel → IsoController.StartAsync. * Logger writes to the same %LOCALAPPDATA%\TeamsISO\Logs as the WPF host so a mixed-host operator sees a single timeline. MainWindow.xaml + .xaml.cs: * x:Name on the section header buttons (RefreshButton, StopAllButton, EnableAllButton, MarkerButton) and on the status bar text + the ParticipantsHost grid. * AttachViewModel wires those buttons to view-model commands; pushes StatusText + ParticipantCountText through PropertyChanged. * BuildSimpleRow imperatively constructs each row (Grid with name + codec + output + ISO toggle pill) instead of going through a DataTemplate. Rationale: declaring a DataTemplate in Grid.Resources OR loading one via XamlReader.Load both crash WinUI 3's XAML parser at runtime on this build host (same HR=0x802b000a we saw with the SettingsDrawer NavigationView). Imperative construction sidesteps the parser. The rich row template (avatar circle, audio meter, active-speaker accent) returns in Phase 5 alongside the CommunityToolkit DataGrid swap. * Per-row PropertyChanged subscriptions refresh DisplayName, codec, output name, and ISO pill text as the engine pushes updates. Verified live (PID 4824, 2026-05-13 08:02): three real Teams participants appeared in the redesigned shell within seconds of launch, status bar populated with "3 participants · 0 routing", section header showed "Participants 3", and the title-bar live pills rendered in their proper places (placeholder values for now; binding to SessionElapsed / IsRecording lands in the next commit).
2026-05-13 08:03:32 -04:00
using TeamsISO.App.WinUI.ViewModels;
feat(winui3): redesigned MainWindow + custom title bar + theme toggle Lands the approved shape brief as the WinUI 3 MainWindow: * 64px left rail with brand mark, primary nav (participants), Teams launch / hide / settings buttons, and the engine-status puck at the bottom. All five rail buttons use Segoe Fluent Icons glyphs at a uniform 20px optical size; no more bespoke <Path Data> shapes with inconsistent stroke weights. * 44px custom title bar via ExtendsContentIntoTitleBar + SetTitleBar(AppTitleBar). The drag region absorbs the three live-state pills inline (session timer 'live * 00:14:32', REC count + elapsed, disk free) and a slim sun/moon theme-toggle button to the left of the system Min/Max/Close controls. System buttons inherit ButtonForeground Color etc. from AppWindow.TitleBar so they match palette in both themes. * Section header with 'Participants * count' display, filter input, Refresh + Presets (Secondary buttons), and 'Enable all online' as the single cyan Primary button - finally a real button hierarchy instead of seven indistinguishable ghost buttons. * Participants list rendered as ItemsRepeater + DataTemplate for now; the CommunityToolkit DataGrid migration follows in a separate commit. Row template at 64px height with: 3px cyan left border for active speaker, avatar with initials in cyan-muted circle, name + codec line, signal lock state with dot, audio meter via ProgressBar, output name in JetBrains Mono, ISO state pill (LIVE/OFF/ERROR) at right. * Conditional in-call control bar below the table: Mute / Camera / Share / Marker / Leave + overflow kebab. Muted state binds the destructive coral treatment to the Mute button; Leave is also destructive (coral border + text); everything else is Secondary. Tight 8px spacing keeps the bar dense without crowding. * Slim 32px status bar at the bottom: control-surface URL on the left (cyan dot indicator), keyboard-shortcut hints on the right in tertiary mono. Replaces the WPF host's six-column footer. Implementation notes: * MockParticipant model populates the table with representative data (Maya / Daniel / Aicha / Sam, one as active speaker) until the ParticipantViewModel binding migrates over from the WPF host. * Custom Program.cs takes ownership of Main from the XAML compiler (DISABLE_XAML_GENERATED_MAIN). Calls Bootstrap.TryInitialize(0x00010006) before Application.Start so the unpackaged .exe can locate the WindowsAppSDK 1.6 framework MSIX at launch. Shutdown is paired in a finally block. * Theme toggle in code-behind flips Window.Content.RequestedTheme between Dark and Light. {ThemeResource} bindings auto-swap across the visual tree; system title-bar buttons (outside the XAML tree) get color updates inline so they stay readable in both modes. * app.manifest deferred from build - the framework-emitted manifest covers DPI awareness and supportedOS GUIDs; reintroducing our own goes in the next commit alongside the bootstrapper hardening. Known issue: the unpackaged .exe currently fails to activate on this build host with 'this application could not be started' before Main runs. Build is clean; published output runs the same way. Diagnosing the activation failure is the next session's first task (likely the runtimeconfig.json including Microsoft.WindowsDesktop.App which WinUI 3 doesn't want, or a missing CRT redistributable). The WPF host remains the running build until that's resolved. dotnet build TeamsISO.Windows.slnf -c Debug: 0 warnings, 0 errors.
2026-05-13 00:03:12 -04:00
using Windows.Graphics;
using Windows.UI;
feat(winui3): scaffold TeamsISO.App.WinUI alongside the WPF host First step of the WinUI 3 replatform per the approved redesign brief. The new project coexists with the existing src/TeamsISO.App (WPF) so the WPF host keeps building and shipping while the WinUI 3 redesign lands incrementally. Once the WinUI 3 build is feature-complete and tested against a real Teams meeting, the WPF project is retired. Scaffold contents: * src/TeamsISO.App.WinUI/TeamsISO.App.WinUI.csproj Windows App SDK 1.6 LTS (250602001), unpackaged mode (WindowsPackageType=None) so the existing MSI installer keeps working. Target framework net8.0-windows10.0.19041.0, min platform 10.0.17763.0 to preserve Win10 1809+ compatibility for working broadcast hardware. Pins WindowsSdkPackageVersion=10.0.19041.38 so .NET SDK 8.0.301 builds cleanly without an SDK upgrade on the build host. * src/TeamsISO.App.WinUI/app.manifest PerMonitorV2 DPI awareness + gdiScaling for crisp text on high-DPI broadcast monitors. asInvoker trust level (control surface :9755 and OSC :9000 bind to 127.0.0.1, no admin needed). * App.xaml + App.xaml.cs Minimal startup: brings up MainWindow. The full pipeline (NDI runtime preflight, IsoController wiring, single-instance mutex, REST + OSC bridge, tray icon, crash diagnostics, auto-update banner, onboarding) migrates in subsequent commits. * Themes/Tokens.xaml Wild Dragon design tokens as ThemeDictionary entries (Default = Dark, Light). Colors as Color resources, Brushes paired per theme so {ThemeResource} auto-swaps when RequestedTheme flips — no app restart, no flicker. Spacing/radii/typography tokens are theme-agnostic at the outer level. Light palette maintains brand recognition via cyan-tinted off-whites (#FAFAFB canvas, #F0F1F3 rail) rather than pure white, and splits cyan into accent.cyan.surface (#97EDF0, works in both modes because text on top is near-black) and accent.cyan.text (#97EDF0 dark / #0E7C82 light) so captions and inline labels keep AA contrast. * Themes/Controls.xaml Button hierarchy with real commitments: Primary (cyan fill, one per surface), Secondary (transparent bordered), Tertiary (text only), Destructive (coral border + text), Caption (titlebar), RailIcon. Typographic ramp (Display / Title / Heading / Body / Subtle / Caption / Mono) at the DESIGN.md 1.25 ratio. * CommunityToolkit.WinUI.UI.Controls.DataGrid 7.1.2 referenced for the participants table migration. (Toolkit 8.x dropped DataGrid; 7.x is the only currently-maintained free option for WinUI 3.) * Inter.ttf + JetBrainsMono.ttf + dragon-mark.png + teamsiso.ico copied from the WPF project's Assets/ so the WinUI 3 host is self-contained. * TeamsISO.sln + TeamsISO.Windows.slnf updated to include the new project. The .slnf paths switch to backslash form so MSBuild can match them against the .sln's canonical path representation. Verified: dotnet build TeamsISO.Windows.slnf -c Debug succeeds with 0 warnings and 0 errors for all 8 projects (WPF host, WinUI 3 host, engine, NDI interop, console, three test projects).
2026-05-12 23:52:35 -04:00
namespace TeamsISO.App.WinUI.Views;
public sealed partial class MainWindow : Window
{
feat(winui3): engine wired — discovers Teams participants live The WinUI 3 host now stands up the full engine pipeline on launch and discovers participants from the operator's live Teams meeting. Verified end-to-end against a real call: window opened, NDI runtime preflight passed, IsoController spun up, and Participants observable yielded three live entries ((Local), Active Speaker, Brendon Power) with their TEAMSISO_<name> output names in the redesigned shell. What this commit lands: ViewModels/ (slim ports of the WPF host's view-models — engine layer shared verbatim via ProjectReference): * ObservableObject.cs — INPC base, mirrors the WPF version * RelayCommand.cs — sync + typed + async variants; ICommand is the same shared type across both hosts (System.ObjectModel.dll) * ParticipantViewModel.cs — DisplayName / Initials / SourceCodec / IsoStateLabel / DisplayedAudioLevel / IsActiveSpeaker / IsOnline / OutputName + ToggleIsoCommand. Drops the WPF-specific thumbnail WriteableBitmap path, clipboard, PreviewWindow, snapshot encoder — those come back when the WinUI imaging pipeline is wired (Phase 5 of the migration plan). * MainViewModel.cs — subscribes to IIsoController.Participants on a DispatcherQueueSynchronizationContext, owns the ObservableCollection, runs a 1Hz DispatcherQueueTimer for stats + active-speaker highlight + session-elapsed text. Commands: EnableAllOnline, StopAllIsos, RefreshDiscovery, DropRecordingMarker, ToggleByIndex. App.xaml.cs: * OnLaunched brings up MainWindow first, then fires WireEngineAsync so the user sees the shell immediately while NDI preflight + engine setup proceed. * Full pipeline: EngineLogging → NdiInteropPInvoke (with friendly fallback message if the NDI runtime isn't installed) → ConfigStore at %APPDATA%\TeamsISO\config.json → NdiRuntimeProbe + scaler → IsoPipeline factory → IsoController → MainViewModel → MainWindow.AttachViewModel → IsoController.StartAsync. * Logger writes to the same %LOCALAPPDATA%\TeamsISO\Logs as the WPF host so a mixed-host operator sees a single timeline. MainWindow.xaml + .xaml.cs: * x:Name on the section header buttons (RefreshButton, StopAllButton, EnableAllButton, MarkerButton) and on the status bar text + the ParticipantsHost grid. * AttachViewModel wires those buttons to view-model commands; pushes StatusText + ParticipantCountText through PropertyChanged. * BuildSimpleRow imperatively constructs each row (Grid with name + codec + output + ISO toggle pill) instead of going through a DataTemplate. Rationale: declaring a DataTemplate in Grid.Resources OR loading one via XamlReader.Load both crash WinUI 3's XAML parser at runtime on this build host (same HR=0x802b000a we saw with the SettingsDrawer NavigationView). Imperative construction sidesteps the parser. The rich row template (avatar circle, audio meter, active-speaker accent) returns in Phase 5 alongside the CommunityToolkit DataGrid swap. * Per-row PropertyChanged subscriptions refresh DisplayName, codec, output name, and ISO pill text as the engine pushes updates. Verified live (PID 4824, 2026-05-13 08:02): three real Teams participants appeared in the redesigned shell within seconds of launch, status bar populated with "3 participants · 0 routing", section header showed "Participants 3", and the title-bar live pills rendered in their proper places (placeholder values for now; binding to SessionElapsed / IsRecording lands in the next commit).
2026-05-13 08:03:32 -04:00
private MainViewModel? _viewModel;
feat(winui3): scaffold TeamsISO.App.WinUI alongside the WPF host First step of the WinUI 3 replatform per the approved redesign brief. The new project coexists with the existing src/TeamsISO.App (WPF) so the WPF host keeps building and shipping while the WinUI 3 redesign lands incrementally. Once the WinUI 3 build is feature-complete and tested against a real Teams meeting, the WPF project is retired. Scaffold contents: * src/TeamsISO.App.WinUI/TeamsISO.App.WinUI.csproj Windows App SDK 1.6 LTS (250602001), unpackaged mode (WindowsPackageType=None) so the existing MSI installer keeps working. Target framework net8.0-windows10.0.19041.0, min platform 10.0.17763.0 to preserve Win10 1809+ compatibility for working broadcast hardware. Pins WindowsSdkPackageVersion=10.0.19041.38 so .NET SDK 8.0.301 builds cleanly without an SDK upgrade on the build host. * src/TeamsISO.App.WinUI/app.manifest PerMonitorV2 DPI awareness + gdiScaling for crisp text on high-DPI broadcast monitors. asInvoker trust level (control surface :9755 and OSC :9000 bind to 127.0.0.1, no admin needed). * App.xaml + App.xaml.cs Minimal startup: brings up MainWindow. The full pipeline (NDI runtime preflight, IsoController wiring, single-instance mutex, REST + OSC bridge, tray icon, crash diagnostics, auto-update banner, onboarding) migrates in subsequent commits. * Themes/Tokens.xaml Wild Dragon design tokens as ThemeDictionary entries (Default = Dark, Light). Colors as Color resources, Brushes paired per theme so {ThemeResource} auto-swaps when RequestedTheme flips — no app restart, no flicker. Spacing/radii/typography tokens are theme-agnostic at the outer level. Light palette maintains brand recognition via cyan-tinted off-whites (#FAFAFB canvas, #F0F1F3 rail) rather than pure white, and splits cyan into accent.cyan.surface (#97EDF0, works in both modes because text on top is near-black) and accent.cyan.text (#97EDF0 dark / #0E7C82 light) so captions and inline labels keep AA contrast. * Themes/Controls.xaml Button hierarchy with real commitments: Primary (cyan fill, one per surface), Secondary (transparent bordered), Tertiary (text only), Destructive (coral border + text), Caption (titlebar), RailIcon. Typographic ramp (Display / Title / Heading / Body / Subtle / Caption / Mono) at the DESIGN.md 1.25 ratio. * CommunityToolkit.WinUI.UI.Controls.DataGrid 7.1.2 referenced for the participants table migration. (Toolkit 8.x dropped DataGrid; 7.x is the only currently-maintained free option for WinUI 3.) * Inter.ttf + JetBrainsMono.ttf + dragon-mark.png + teamsiso.ico copied from the WPF project's Assets/ so the WinUI 3 host is self-contained. * TeamsISO.sln + TeamsISO.Windows.slnf updated to include the new project. The .slnf paths switch to backslash form so MSBuild can match them against the .sln's canonical path representation. Verified: dotnet build TeamsISO.Windows.slnf -c Debug succeeds with 0 warnings and 0 errors for all 8 projects (WPF host, WinUI 3 host, engine, NDI interop, console, three test projects).
2026-05-12 23:52:35 -04:00
public MainWindow()
{
InitializeComponent();
feat(winui3): redesigned MainWindow + custom title bar + theme toggle Lands the approved shape brief as the WinUI 3 MainWindow: * 64px left rail with brand mark, primary nav (participants), Teams launch / hide / settings buttons, and the engine-status puck at the bottom. All five rail buttons use Segoe Fluent Icons glyphs at a uniform 20px optical size; no more bespoke <Path Data> shapes with inconsistent stroke weights. * 44px custom title bar via ExtendsContentIntoTitleBar + SetTitleBar(AppTitleBar). The drag region absorbs the three live-state pills inline (session timer 'live * 00:14:32', REC count + elapsed, disk free) and a slim sun/moon theme-toggle button to the left of the system Min/Max/Close controls. System buttons inherit ButtonForeground Color etc. from AppWindow.TitleBar so they match palette in both themes. * Section header with 'Participants * count' display, filter input, Refresh + Presets (Secondary buttons), and 'Enable all online' as the single cyan Primary button - finally a real button hierarchy instead of seven indistinguishable ghost buttons. * Participants list rendered as ItemsRepeater + DataTemplate for now; the CommunityToolkit DataGrid migration follows in a separate commit. Row template at 64px height with: 3px cyan left border for active speaker, avatar with initials in cyan-muted circle, name + codec line, signal lock state with dot, audio meter via ProgressBar, output name in JetBrains Mono, ISO state pill (LIVE/OFF/ERROR) at right. * Conditional in-call control bar below the table: Mute / Camera / Share / Marker / Leave + overflow kebab. Muted state binds the destructive coral treatment to the Mute button; Leave is also destructive (coral border + text); everything else is Secondary. Tight 8px spacing keeps the bar dense without crowding. * Slim 32px status bar at the bottom: control-surface URL on the left (cyan dot indicator), keyboard-shortcut hints on the right in tertiary mono. Replaces the WPF host's six-column footer. Implementation notes: * MockParticipant model populates the table with representative data (Maya / Daniel / Aicha / Sam, one as active speaker) until the ParticipantViewModel binding migrates over from the WPF host. * Custom Program.cs takes ownership of Main from the XAML compiler (DISABLE_XAML_GENERATED_MAIN). Calls Bootstrap.TryInitialize(0x00010006) before Application.Start so the unpackaged .exe can locate the WindowsAppSDK 1.6 framework MSIX at launch. Shutdown is paired in a finally block. * Theme toggle in code-behind flips Window.Content.RequestedTheme between Dark and Light. {ThemeResource} bindings auto-swap across the visual tree; system title-bar buttons (outside the XAML tree) get color updates inline so they stay readable in both modes. * app.manifest deferred from build - the framework-emitted manifest covers DPI awareness and supportedOS GUIDs; reintroducing our own goes in the next commit alongside the bootstrapper hardening. Known issue: the unpackaged .exe currently fails to activate on this build host with 'this application could not be started' before Main runs. Build is clean; published output runs the same way. Diagnosing the activation failure is the next session's first task (likely the runtimeconfig.json including Microsoft.WindowsDesktop.App which WinUI 3 doesn't want, or a missing CRT redistributable). The WPF host remains the running build until that's resolved. dotnet build TeamsISO.Windows.slnf -c Debug: 0 warnings, 0 errors.
2026-05-13 00:03:12 -04:00
feat(winui3): scaffold TeamsISO.App.WinUI alongside the WPF host First step of the WinUI 3 replatform per the approved redesign brief. The new project coexists with the existing src/TeamsISO.App (WPF) so the WPF host keeps building and shipping while the WinUI 3 redesign lands incrementally. Once the WinUI 3 build is feature-complete and tested against a real Teams meeting, the WPF project is retired. Scaffold contents: * src/TeamsISO.App.WinUI/TeamsISO.App.WinUI.csproj Windows App SDK 1.6 LTS (250602001), unpackaged mode (WindowsPackageType=None) so the existing MSI installer keeps working. Target framework net8.0-windows10.0.19041.0, min platform 10.0.17763.0 to preserve Win10 1809+ compatibility for working broadcast hardware. Pins WindowsSdkPackageVersion=10.0.19041.38 so .NET SDK 8.0.301 builds cleanly without an SDK upgrade on the build host. * src/TeamsISO.App.WinUI/app.manifest PerMonitorV2 DPI awareness + gdiScaling for crisp text on high-DPI broadcast monitors. asInvoker trust level (control surface :9755 and OSC :9000 bind to 127.0.0.1, no admin needed). * App.xaml + App.xaml.cs Minimal startup: brings up MainWindow. The full pipeline (NDI runtime preflight, IsoController wiring, single-instance mutex, REST + OSC bridge, tray icon, crash diagnostics, auto-update banner, onboarding) migrates in subsequent commits. * Themes/Tokens.xaml Wild Dragon design tokens as ThemeDictionary entries (Default = Dark, Light). Colors as Color resources, Brushes paired per theme so {ThemeResource} auto-swaps when RequestedTheme flips — no app restart, no flicker. Spacing/radii/typography tokens are theme-agnostic at the outer level. Light palette maintains brand recognition via cyan-tinted off-whites (#FAFAFB canvas, #F0F1F3 rail) rather than pure white, and splits cyan into accent.cyan.surface (#97EDF0, works in both modes because text on top is near-black) and accent.cyan.text (#97EDF0 dark / #0E7C82 light) so captions and inline labels keep AA contrast. * Themes/Controls.xaml Button hierarchy with real commitments: Primary (cyan fill, one per surface), Secondary (transparent bordered), Tertiary (text only), Destructive (coral border + text), Caption (titlebar), RailIcon. Typographic ramp (Display / Title / Heading / Body / Subtle / Caption / Mono) at the DESIGN.md 1.25 ratio. * CommunityToolkit.WinUI.UI.Controls.DataGrid 7.1.2 referenced for the participants table migration. (Toolkit 8.x dropped DataGrid; 7.x is the only currently-maintained free option for WinUI 3.) * Inter.ttf + JetBrainsMono.ttf + dragon-mark.png + teamsiso.ico copied from the WPF project's Assets/ so the WinUI 3 host is self-contained. * TeamsISO.sln + TeamsISO.Windows.slnf updated to include the new project. The .slnf paths switch to backslash form so MSBuild can match them against the .sln's canonical path representation. Verified: dotnet build TeamsISO.Windows.slnf -c Debug succeeds with 0 warnings and 0 errors for all 8 projects (WPF host, WinUI 3 host, engine, NDI interop, console, three test projects).
2026-05-12 23:52:35 -04:00
Title = "TeamsISO";
feat(winui3): redesigned MainWindow + custom title bar + theme toggle Lands the approved shape brief as the WinUI 3 MainWindow: * 64px left rail with brand mark, primary nav (participants), Teams launch / hide / settings buttons, and the engine-status puck at the bottom. All five rail buttons use Segoe Fluent Icons glyphs at a uniform 20px optical size; no more bespoke <Path Data> shapes with inconsistent stroke weights. * 44px custom title bar via ExtendsContentIntoTitleBar + SetTitleBar(AppTitleBar). The drag region absorbs the three live-state pills inline (session timer 'live * 00:14:32', REC count + elapsed, disk free) and a slim sun/moon theme-toggle button to the left of the system Min/Max/Close controls. System buttons inherit ButtonForeground Color etc. from AppWindow.TitleBar so they match palette in both themes. * Section header with 'Participants * count' display, filter input, Refresh + Presets (Secondary buttons), and 'Enable all online' as the single cyan Primary button - finally a real button hierarchy instead of seven indistinguishable ghost buttons. * Participants list rendered as ItemsRepeater + DataTemplate for now; the CommunityToolkit DataGrid migration follows in a separate commit. Row template at 64px height with: 3px cyan left border for active speaker, avatar with initials in cyan-muted circle, name + codec line, signal lock state with dot, audio meter via ProgressBar, output name in JetBrains Mono, ISO state pill (LIVE/OFF/ERROR) at right. * Conditional in-call control bar below the table: Mute / Camera / Share / Marker / Leave + overflow kebab. Muted state binds the destructive coral treatment to the Mute button; Leave is also destructive (coral border + text); everything else is Secondary. Tight 8px spacing keeps the bar dense without crowding. * Slim 32px status bar at the bottom: control-surface URL on the left (cyan dot indicator), keyboard-shortcut hints on the right in tertiary mono. Replaces the WPF host's six-column footer. Implementation notes: * MockParticipant model populates the table with representative data (Maya / Daniel / Aicha / Sam, one as active speaker) until the ParticipantViewModel binding migrates over from the WPF host. * Custom Program.cs takes ownership of Main from the XAML compiler (DISABLE_XAML_GENERATED_MAIN). Calls Bootstrap.TryInitialize(0x00010006) before Application.Start so the unpackaged .exe can locate the WindowsAppSDK 1.6 framework MSIX at launch. Shutdown is paired in a finally block. * Theme toggle in code-behind flips Window.Content.RequestedTheme between Dark and Light. {ThemeResource} bindings auto-swap across the visual tree; system title-bar buttons (outside the XAML tree) get color updates inline so they stay readable in both modes. * app.manifest deferred from build - the framework-emitted manifest covers DPI awareness and supportedOS GUIDs; reintroducing our own goes in the next commit alongside the bootstrapper hardening. Known issue: the unpackaged .exe currently fails to activate on this build host with 'this application could not be started' before Main runs. Build is clean; published output runs the same way. Diagnosing the activation failure is the next session's first task (likely the runtimeconfig.json including Microsoft.WindowsDesktop.App which WinUI 3 doesn't want, or a missing CRT redistributable). The WPF host remains the running build until that's resolved. dotnet build TeamsISO.Windows.slnf -c Debug: 0 warnings, 0 errors.
2026-05-13 00:03:12 -04:00
ExtendsContentIntoTitleBar = true;
SetTitleBar(AppTitleBar);
AppWindow.TitleBar.ButtonBackgroundColor = Colors.Transparent;
AppWindow.TitleBar.ButtonInactiveBackgroundColor = Colors.Transparent;
AppWindow.TitleBar.ButtonHoverForegroundColor = Colors.White;
AppWindow.Resize(new SizeInt32(1280, 780));
feat(winui3): ThemeManager service + Settings drawer + Help/About/Onboarding 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).
2026-05-13 00:13:58 -04:00
ThemeManager.Current.Themed += (_, theme) => ApplyResolvedTheme(theme);
ApplyResolvedTheme(ThemeManager.Current.ResolveTheme());
feat(winui3): redesigned MainWindow + custom title bar + theme toggle Lands the approved shape brief as the WinUI 3 MainWindow: * 64px left rail with brand mark, primary nav (participants), Teams launch / hide / settings buttons, and the engine-status puck at the bottom. All five rail buttons use Segoe Fluent Icons glyphs at a uniform 20px optical size; no more bespoke <Path Data> shapes with inconsistent stroke weights. * 44px custom title bar via ExtendsContentIntoTitleBar + SetTitleBar(AppTitleBar). The drag region absorbs the three live-state pills inline (session timer 'live * 00:14:32', REC count + elapsed, disk free) and a slim sun/moon theme-toggle button to the left of the system Min/Max/Close controls. System buttons inherit ButtonForeground Color etc. from AppWindow.TitleBar so they match palette in both themes. * Section header with 'Participants * count' display, filter input, Refresh + Presets (Secondary buttons), and 'Enable all online' as the single cyan Primary button - finally a real button hierarchy instead of seven indistinguishable ghost buttons. * Participants list rendered as ItemsRepeater + DataTemplate for now; the CommunityToolkit DataGrid migration follows in a separate commit. Row template at 64px height with: 3px cyan left border for active speaker, avatar with initials in cyan-muted circle, name + codec line, signal lock state with dot, audio meter via ProgressBar, output name in JetBrains Mono, ISO state pill (LIVE/OFF/ERROR) at right. * Conditional in-call control bar below the table: Mute / Camera / Share / Marker / Leave + overflow kebab. Muted state binds the destructive coral treatment to the Mute button; Leave is also destructive (coral border + text); everything else is Secondary. Tight 8px spacing keeps the bar dense without crowding. * Slim 32px status bar at the bottom: control-surface URL on the left (cyan dot indicator), keyboard-shortcut hints on the right in tertiary mono. Replaces the WPF host's six-column footer. Implementation notes: * MockParticipant model populates the table with representative data (Maya / Daniel / Aicha / Sam, one as active speaker) until the ParticipantViewModel binding migrates over from the WPF host. * Custom Program.cs takes ownership of Main from the XAML compiler (DISABLE_XAML_GENERATED_MAIN). Calls Bootstrap.TryInitialize(0x00010006) before Application.Start so the unpackaged .exe can locate the WindowsAppSDK 1.6 framework MSIX at launch. Shutdown is paired in a finally block. * Theme toggle in code-behind flips Window.Content.RequestedTheme between Dark and Light. {ThemeResource} bindings auto-swap across the visual tree; system title-bar buttons (outside the XAML tree) get color updates inline so they stay readable in both modes. * app.manifest deferred from build - the framework-emitted manifest covers DPI awareness and supportedOS GUIDs; reintroducing our own goes in the next commit alongside the bootstrapper hardening. Known issue: the unpackaged .exe currently fails to activate on this build host with 'this application could not be started' before Main runs. Build is clean; published output runs the same way. Diagnosing the activation failure is the next session's first task (likely the runtimeconfig.json including Microsoft.WindowsDesktop.App which WinUI 3 doesn't want, or a missing CRT redistributable). The WPF host remains the running build until that's resolved. dotnet build TeamsISO.Windows.slnf -c Debug: 0 warnings, 0 errors.
2026-05-13 00:03:12 -04:00
}
/// <summary>
feat(winui3): engine wired — discovers Teams participants live The WinUI 3 host now stands up the full engine pipeline on launch and discovers participants from the operator's live Teams meeting. Verified end-to-end against a real call: window opened, NDI runtime preflight passed, IsoController spun up, and Participants observable yielded three live entries ((Local), Active Speaker, Brendon Power) with their TEAMSISO_<name> output names in the redesigned shell. What this commit lands: ViewModels/ (slim ports of the WPF host's view-models — engine layer shared verbatim via ProjectReference): * ObservableObject.cs — INPC base, mirrors the WPF version * RelayCommand.cs — sync + typed + async variants; ICommand is the same shared type across both hosts (System.ObjectModel.dll) * ParticipantViewModel.cs — DisplayName / Initials / SourceCodec / IsoStateLabel / DisplayedAudioLevel / IsActiveSpeaker / IsOnline / OutputName + ToggleIsoCommand. Drops the WPF-specific thumbnail WriteableBitmap path, clipboard, PreviewWindow, snapshot encoder — those come back when the WinUI imaging pipeline is wired (Phase 5 of the migration plan). * MainViewModel.cs — subscribes to IIsoController.Participants on a DispatcherQueueSynchronizationContext, owns the ObservableCollection, runs a 1Hz DispatcherQueueTimer for stats + active-speaker highlight + session-elapsed text. Commands: EnableAllOnline, StopAllIsos, RefreshDiscovery, DropRecordingMarker, ToggleByIndex. App.xaml.cs: * OnLaunched brings up MainWindow first, then fires WireEngineAsync so the user sees the shell immediately while NDI preflight + engine setup proceed. * Full pipeline: EngineLogging → NdiInteropPInvoke (with friendly fallback message if the NDI runtime isn't installed) → ConfigStore at %APPDATA%\TeamsISO\config.json → NdiRuntimeProbe + scaler → IsoPipeline factory → IsoController → MainViewModel → MainWindow.AttachViewModel → IsoController.StartAsync. * Logger writes to the same %LOCALAPPDATA%\TeamsISO\Logs as the WPF host so a mixed-host operator sees a single timeline. MainWindow.xaml + .xaml.cs: * x:Name on the section header buttons (RefreshButton, StopAllButton, EnableAllButton, MarkerButton) and on the status bar text + the ParticipantsHost grid. * AttachViewModel wires those buttons to view-model commands; pushes StatusText + ParticipantCountText through PropertyChanged. * BuildSimpleRow imperatively constructs each row (Grid with name + codec + output + ISO toggle pill) instead of going through a DataTemplate. Rationale: declaring a DataTemplate in Grid.Resources OR loading one via XamlReader.Load both crash WinUI 3's XAML parser at runtime on this build host (same HR=0x802b000a we saw with the SettingsDrawer NavigationView). Imperative construction sidesteps the parser. The rich row template (avatar circle, audio meter, active-speaker accent) returns in Phase 5 alongside the CommunityToolkit DataGrid swap. * Per-row PropertyChanged subscriptions refresh DisplayName, codec, output name, and ISO pill text as the engine pushes updates. Verified live (PID 4824, 2026-05-13 08:02): three real Teams participants appeared in the redesigned shell within seconds of launch, status bar populated with "3 participants · 0 routing", section header showed "Participants 3", and the title-bar live pills rendered in their proper places (placeholder values for now; binding to SessionElapsed / IsRecording lands in the next commit).
2026-05-13 08:03:32 -04:00
/// Hook the engine view-model in. Replaces the placeholder StackPanel
/// inside ParticipantsHost with a live ListView. Rather than fight WinUI
/// 3's DataTemplate compilation, we subscribe to the Participants
/// collection and rebuild a simple StackPanel of row controls on
/// every change. Less efficient than a virtualized ListView for huge
/// lists, fine for the operator's ~10 max participants.
/// </summary>
public void AttachViewModel(MainViewModel viewModel)
{
_viewModel = viewModel;
// Section header + in-call buttons → view-model commands.
// The buttons exist in MainWindow.xaml with the matching x:Names.
RefreshButton.Command = viewModel.RefreshDiscoveryCommand;
EnableAllButton.Command = viewModel.EnableAllOnlineCommand;
StopAllButton.Command = viewModel.StopAllIsosCommand;
MarkerButton.Command = viewModel.DropRecordingMarkerCommand;
// Status bar + participant count text refresh on VM property changes.
ParticipantCountText.Text = viewModel.ParticipantCountText;
StatusBarText.Text = viewModel.StatusText;
viewModel.PropertyChanged += (_, e) =>
{
DispatcherQueue.TryEnqueue(() =>
{
switch (e.PropertyName)
{
case nameof(MainViewModel.ParticipantCountText):
ParticipantCountText.Text = viewModel.ParticipantCountText;
break;
case nameof(MainViewModel.StatusText):
StatusBarText.Text = viewModel.StatusText;
break;
}
});
};
ParticipantsHost.Children.Clear();
var stack = new Microsoft.UI.Xaml.Controls.StackPanel
{
HorizontalAlignment = HorizontalAlignment.Stretch,
VerticalAlignment = VerticalAlignment.Top,
};
var scroll = new Microsoft.UI.Xaml.Controls.ScrollViewer
{
VerticalScrollBarVisibility = Microsoft.UI.Xaml.Controls.ScrollBarVisibility.Auto,
Content = stack,
};
ParticipantsHost.Children.Add(scroll);
void Rebuild()
{
stack.Children.Clear();
foreach (var p in viewModel.Participants)
{
stack.Children.Add(BuildSimpleRow(p));
}
}
viewModel.Participants.CollectionChanged += (_, _) =>
{
DispatcherQueue.TryEnqueue(Rebuild);
};
Rebuild();
}
/// <summary>
/// Minimal participant row — name + ISO state + toggle button. Drops
/// the brushed avatar / theme-resource lookups that may have been
/// triggering the crash. The full visual row template comes back
/// after we've verified the binding path works.
feat(winui3): redesigned MainWindow + custom title bar + theme toggle Lands the approved shape brief as the WinUI 3 MainWindow: * 64px left rail with brand mark, primary nav (participants), Teams launch / hide / settings buttons, and the engine-status puck at the bottom. All five rail buttons use Segoe Fluent Icons glyphs at a uniform 20px optical size; no more bespoke <Path Data> shapes with inconsistent stroke weights. * 44px custom title bar via ExtendsContentIntoTitleBar + SetTitleBar(AppTitleBar). The drag region absorbs the three live-state pills inline (session timer 'live * 00:14:32', REC count + elapsed, disk free) and a slim sun/moon theme-toggle button to the left of the system Min/Max/Close controls. System buttons inherit ButtonForeground Color etc. from AppWindow.TitleBar so they match palette in both themes. * Section header with 'Participants * count' display, filter input, Refresh + Presets (Secondary buttons), and 'Enable all online' as the single cyan Primary button - finally a real button hierarchy instead of seven indistinguishable ghost buttons. * Participants list rendered as ItemsRepeater + DataTemplate for now; the CommunityToolkit DataGrid migration follows in a separate commit. Row template at 64px height with: 3px cyan left border for active speaker, avatar with initials in cyan-muted circle, name + codec line, signal lock state with dot, audio meter via ProgressBar, output name in JetBrains Mono, ISO state pill (LIVE/OFF/ERROR) at right. * Conditional in-call control bar below the table: Mute / Camera / Share / Marker / Leave + overflow kebab. Muted state binds the destructive coral treatment to the Mute button; Leave is also destructive (coral border + text); everything else is Secondary. Tight 8px spacing keeps the bar dense without crowding. * Slim 32px status bar at the bottom: control-surface URL on the left (cyan dot indicator), keyboard-shortcut hints on the right in tertiary mono. Replaces the WPF host's six-column footer. Implementation notes: * MockParticipant model populates the table with representative data (Maya / Daniel / Aicha / Sam, one as active speaker) until the ParticipantViewModel binding migrates over from the WPF host. * Custom Program.cs takes ownership of Main from the XAML compiler (DISABLE_XAML_GENERATED_MAIN). Calls Bootstrap.TryInitialize(0x00010006) before Application.Start so the unpackaged .exe can locate the WindowsAppSDK 1.6 framework MSIX at launch. Shutdown is paired in a finally block. * Theme toggle in code-behind flips Window.Content.RequestedTheme between Dark and Light. {ThemeResource} bindings auto-swap across the visual tree; system title-bar buttons (outside the XAML tree) get color updates inline so they stay readable in both modes. * app.manifest deferred from build - the framework-emitted manifest covers DPI awareness and supportedOS GUIDs; reintroducing our own goes in the next commit alongside the bootstrapper hardening. Known issue: the unpackaged .exe currently fails to activate on this build host with 'this application could not be started' before Main runs. Build is clean; published output runs the same way. Diagnosing the activation failure is the next session's first task (likely the runtimeconfig.json including Microsoft.WindowsDesktop.App which WinUI 3 doesn't want, or a missing CRT redistributable). The WPF host remains the running build until that's resolved. dotnet build TeamsISO.Windows.slnf -c Debug: 0 warnings, 0 errors.
2026-05-13 00:03:12 -04:00
/// </summary>
feat(winui3): engine wired — discovers Teams participants live The WinUI 3 host now stands up the full engine pipeline on launch and discovers participants from the operator's live Teams meeting. Verified end-to-end against a real call: window opened, NDI runtime preflight passed, IsoController spun up, and Participants observable yielded three live entries ((Local), Active Speaker, Brendon Power) with their TEAMSISO_<name> output names in the redesigned shell. What this commit lands: ViewModels/ (slim ports of the WPF host's view-models — engine layer shared verbatim via ProjectReference): * ObservableObject.cs — INPC base, mirrors the WPF version * RelayCommand.cs — sync + typed + async variants; ICommand is the same shared type across both hosts (System.ObjectModel.dll) * ParticipantViewModel.cs — DisplayName / Initials / SourceCodec / IsoStateLabel / DisplayedAudioLevel / IsActiveSpeaker / IsOnline / OutputName + ToggleIsoCommand. Drops the WPF-specific thumbnail WriteableBitmap path, clipboard, PreviewWindow, snapshot encoder — those come back when the WinUI imaging pipeline is wired (Phase 5 of the migration plan). * MainViewModel.cs — subscribes to IIsoController.Participants on a DispatcherQueueSynchronizationContext, owns the ObservableCollection, runs a 1Hz DispatcherQueueTimer for stats + active-speaker highlight + session-elapsed text. Commands: EnableAllOnline, StopAllIsos, RefreshDiscovery, DropRecordingMarker, ToggleByIndex. App.xaml.cs: * OnLaunched brings up MainWindow first, then fires WireEngineAsync so the user sees the shell immediately while NDI preflight + engine setup proceed. * Full pipeline: EngineLogging → NdiInteropPInvoke (with friendly fallback message if the NDI runtime isn't installed) → ConfigStore at %APPDATA%\TeamsISO\config.json → NdiRuntimeProbe + scaler → IsoPipeline factory → IsoController → MainViewModel → MainWindow.AttachViewModel → IsoController.StartAsync. * Logger writes to the same %LOCALAPPDATA%\TeamsISO\Logs as the WPF host so a mixed-host operator sees a single timeline. MainWindow.xaml + .xaml.cs: * x:Name on the section header buttons (RefreshButton, StopAllButton, EnableAllButton, MarkerButton) and on the status bar text + the ParticipantsHost grid. * AttachViewModel wires those buttons to view-model commands; pushes StatusText + ParticipantCountText through PropertyChanged. * BuildSimpleRow imperatively constructs each row (Grid with name + codec + output + ISO toggle pill) instead of going through a DataTemplate. Rationale: declaring a DataTemplate in Grid.Resources OR loading one via XamlReader.Load both crash WinUI 3's XAML parser at runtime on this build host (same HR=0x802b000a we saw with the SettingsDrawer NavigationView). Imperative construction sidesteps the parser. The rich row template (avatar circle, audio meter, active-speaker accent) returns in Phase 5 alongside the CommunityToolkit DataGrid swap. * Per-row PropertyChanged subscriptions refresh DisplayName, codec, output name, and ISO pill text as the engine pushes updates. Verified live (PID 4824, 2026-05-13 08:02): three real Teams participants appeared in the redesigned shell within seconds of launch, status bar populated with "3 participants · 0 routing", section header showed "Participants 3", and the title-bar live pills rendered in their proper places (placeholder values for now; binding to SessionElapsed / IsRecording lands in the next commit).
2026-05-13 08:03:32 -04:00
private static Microsoft.UI.Xaml.Controls.Grid BuildSimpleRow(ParticipantViewModel p)
{
var grid = new Microsoft.UI.Xaml.Controls.Grid
{
Height = 56,
Padding = new Thickness(20, 0, 20, 0),
};
grid.ColumnDefinitions.Add(new Microsoft.UI.Xaml.Controls.ColumnDefinition { Width = new Microsoft.UI.Xaml.GridLength(1, Microsoft.UI.Xaml.GridUnitType.Star) });
grid.ColumnDefinitions.Add(new Microsoft.UI.Xaml.Controls.ColumnDefinition { Width = new Microsoft.UI.Xaml.GridLength(120) });
grid.ColumnDefinitions.Add(new Microsoft.UI.Xaml.Controls.ColumnDefinition { Width = Microsoft.UI.Xaml.GridLength.Auto });
var nameStack = new Microsoft.UI.Xaml.Controls.StackPanel
{
VerticalAlignment = VerticalAlignment.Center,
Spacing = 2,
};
var nameText = new Microsoft.UI.Xaml.Controls.TextBlock
{
Text = p.DisplayName,
FontSize = 14,
FontWeight = Microsoft.UI.Text.FontWeights.Medium,
};
var codecText = new Microsoft.UI.Xaml.Controls.TextBlock
{
Text = p.SourceCodec,
FontSize = 11,
Opacity = 0.6,
};
nameStack.Children.Add(nameText);
nameStack.Children.Add(codecText);
Microsoft.UI.Xaml.Controls.Grid.SetColumn(nameStack, 0);
grid.Children.Add(nameStack);
var outputText = new Microsoft.UI.Xaml.Controls.TextBlock
{
Text = p.OutputName,
FontFamily = new Microsoft.UI.Xaml.Media.FontFamily("Consolas"),
FontSize = 12,
VerticalAlignment = VerticalAlignment.Center,
};
Microsoft.UI.Xaml.Controls.Grid.SetColumn(outputText, 1);
grid.Children.Add(outputText);
var pillText = new Microsoft.UI.Xaml.Controls.TextBlock
{
Text = p.IsoStateLabel,
FontSize = 11,
FontWeight = Microsoft.UI.Text.FontWeights.SemiBold,
HorizontalAlignment = HorizontalAlignment.Center,
};
var pill = new Microsoft.UI.Xaml.Controls.Button
{
Command = p.ToggleIsoCommand,
MinWidth = 80,
Padding = new Thickness(14, 6, 14, 6),
CornerRadius = new CornerRadius(999),
VerticalAlignment = VerticalAlignment.Center,
Content = pillText,
};
Microsoft.UI.Xaml.Controls.Grid.SetColumn(pill, 2);
grid.Children.Add(pill);
p.PropertyChanged += (_, e) =>
{
grid.DispatcherQueue.TryEnqueue(() =>
{
switch (e.PropertyName)
{
case nameof(ParticipantViewModel.DisplayName):
nameText.Text = p.DisplayName;
break;
case nameof(ParticipantViewModel.SourceCodec):
codecText.Text = p.SourceCodec;
break;
case nameof(ParticipantViewModel.OutputName):
outputText.Text = p.OutputName;
break;
case nameof(ParticipantViewModel.IsoStateLabel):
case nameof(ParticipantViewModel.IsEnabled):
pillText.Text = p.IsoStateLabel;
break;
}
});
};
return grid;
}
/// <summary>Full rich row template — replaces BuildSimpleRow once we've verified the simple version doesn't crash.</summary>
private static Microsoft.UI.Xaml.Controls.Grid BuildParticipantRow(ParticipantViewModel p)
{
var grid = new Microsoft.UI.Xaml.Controls.Grid
{
Height = 64,
Padding = new Thickness(14, 0, 12, 0),
BorderBrush = (Microsoft.UI.Xaml.Media.Brush)Application.Current.Resources["BorderSubtle"],
BorderThickness = new Thickness(0, 0, 0, 1),
};
grid.ColumnDefinitions.Add(new Microsoft.UI.Xaml.Controls.ColumnDefinition { Width = new Microsoft.UI.Xaml.GridLength(44) });
grid.ColumnDefinitions.Add(new Microsoft.UI.Xaml.Controls.ColumnDefinition { Width = new Microsoft.UI.Xaml.GridLength(2, Microsoft.UI.Xaml.GridUnitType.Star) });
grid.ColumnDefinitions.Add(new Microsoft.UI.Xaml.Controls.ColumnDefinition { Width = new Microsoft.UI.Xaml.GridLength(1.4, Microsoft.UI.Xaml.GridUnitType.Star) });
grid.ColumnDefinitions.Add(new Microsoft.UI.Xaml.Controls.ColumnDefinition { Width = new Microsoft.UI.Xaml.GridLength(1.6, Microsoft.UI.Xaml.GridUnitType.Star) });
grid.ColumnDefinitions.Add(new Microsoft.UI.Xaml.Controls.ColumnDefinition { Width = Microsoft.UI.Xaml.GridLength.Auto });
// Avatar
var avatar = new Microsoft.UI.Xaml.Controls.Border
{
Width = 36, Height = 36,
CornerRadius = new CornerRadius(18),
Background = (Microsoft.UI.Xaml.Media.Brush)Application.Current.Resources["AccentCyanMuted"],
VerticalAlignment = VerticalAlignment.Center,
};
var initialsText = new Microsoft.UI.Xaml.Controls.TextBlock
{
Text = p.Initials,
FontSize = 13,
FontWeight = Microsoft.UI.Text.FontWeights.SemiBold,
Foreground = (Microsoft.UI.Xaml.Media.Brush)Application.Current.Resources["AccentCyanText"],
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
};
avatar.Child = initialsText;
Microsoft.UI.Xaml.Controls.Grid.SetColumn(avatar, 0);
grid.Children.Add(avatar);
// Name + codec
var nameStack = new Microsoft.UI.Xaml.Controls.StackPanel
{
Margin = new Thickness(12, 0, 0, 0),
VerticalAlignment = VerticalAlignment.Center,
Spacing = 2,
};
var nameText = new Microsoft.UI.Xaml.Controls.TextBlock
{
Text = p.DisplayName,
FontSize = 13,
FontWeight = Microsoft.UI.Text.FontWeights.Medium,
Foreground = (Microsoft.UI.Xaml.Media.Brush)Application.Current.Resources["FgPrimary"],
};
var codecText = new Microsoft.UI.Xaml.Controls.TextBlock
{
Text = p.SourceCodec,
FontSize = 11,
Foreground = (Microsoft.UI.Xaml.Media.Brush)Application.Current.Resources["FgSecondary"],
};
nameStack.Children.Add(nameText);
nameStack.Children.Add(codecText);
Microsoft.UI.Xaml.Controls.Grid.SetColumn(nameStack, 1);
grid.Children.Add(nameStack);
// Audio meter
var meter = new Microsoft.UI.Xaml.Controls.ProgressBar
{
Maximum = 1.0,
Value = p.DisplayedAudioLevel,
Height = 4,
Margin = new Thickness(12, 0, 12, 0),
VerticalAlignment = VerticalAlignment.Center,
};
Microsoft.UI.Xaml.Controls.Grid.SetColumn(meter, 2);
grid.Children.Add(meter);
// Output name
var outputText = new Microsoft.UI.Xaml.Controls.TextBlock
{
Text = p.OutputName,
FontFamily = new Microsoft.UI.Xaml.Media.FontFamily("Consolas"),
FontSize = 12,
Foreground = (Microsoft.UI.Xaml.Media.Brush)Application.Current.Resources["FgPrimary"],
VerticalAlignment = VerticalAlignment.Center,
};
Microsoft.UI.Xaml.Controls.Grid.SetColumn(outputText, 3);
grid.Children.Add(outputText);
// ISO toggle pill
var pillText = new Microsoft.UI.Xaml.Controls.TextBlock
{
Text = p.IsoStateLabel,
FontSize = 11,
FontWeight = Microsoft.UI.Text.FontWeights.SemiBold,
Foreground = (Microsoft.UI.Xaml.Media.Brush)Application.Current.Resources["FgPrimary"],
HorizontalAlignment = HorizontalAlignment.Center,
};
var pill = new Microsoft.UI.Xaml.Controls.Button
{
Command = p.ToggleIsoCommand,
MinWidth = 80,
Padding = new Thickness(14, 6, 14, 6),
Background = (Microsoft.UI.Xaml.Media.Brush)Application.Current.Resources["BgSurface"],
BorderBrush = (Microsoft.UI.Xaml.Media.Brush)Application.Current.Resources["BorderStrong"],
BorderThickness = new Thickness(1),
CornerRadius = new CornerRadius(999),
VerticalAlignment = VerticalAlignment.Center,
Content = pillText,
};
Microsoft.UI.Xaml.Controls.Grid.SetColumn(pill, 4);
grid.Children.Add(pill);
// Per-row property-change subscription — refresh text as the
// engine pushes updates.
p.PropertyChanged += (_, e) =>
{
grid.DispatcherQueue.TryEnqueue(() =>
{
switch (e.PropertyName)
{
case nameof(ParticipantViewModel.DisplayName):
nameText.Text = p.DisplayName;
initialsText.Text = p.Initials;
break;
case nameof(ParticipantViewModel.SourceCodec):
codecText.Text = p.SourceCodec;
break;
case nameof(ParticipantViewModel.DisplayedAudioLevel):
meter.Value = p.DisplayedAudioLevel;
break;
case nameof(ParticipantViewModel.OutputName):
outputText.Text = p.OutputName;
break;
case nameof(ParticipantViewModel.IsoStateLabel):
case nameof(ParticipantViewModel.IsEnabled):
pillText.Text = p.IsoStateLabel;
break;
}
});
};
return grid;
}
feat(winui3): redesigned MainWindow + custom title bar + theme toggle Lands the approved shape brief as the WinUI 3 MainWindow: * 64px left rail with brand mark, primary nav (participants), Teams launch / hide / settings buttons, and the engine-status puck at the bottom. All five rail buttons use Segoe Fluent Icons glyphs at a uniform 20px optical size; no more bespoke <Path Data> shapes with inconsistent stroke weights. * 44px custom title bar via ExtendsContentIntoTitleBar + SetTitleBar(AppTitleBar). The drag region absorbs the three live-state pills inline (session timer 'live * 00:14:32', REC count + elapsed, disk free) and a slim sun/moon theme-toggle button to the left of the system Min/Max/Close controls. System buttons inherit ButtonForeground Color etc. from AppWindow.TitleBar so they match palette in both themes. * Section header with 'Participants * count' display, filter input, Refresh + Presets (Secondary buttons), and 'Enable all online' as the single cyan Primary button - finally a real button hierarchy instead of seven indistinguishable ghost buttons. * Participants list rendered as ItemsRepeater + DataTemplate for now; the CommunityToolkit DataGrid migration follows in a separate commit. Row template at 64px height with: 3px cyan left border for active speaker, avatar with initials in cyan-muted circle, name + codec line, signal lock state with dot, audio meter via ProgressBar, output name in JetBrains Mono, ISO state pill (LIVE/OFF/ERROR) at right. * Conditional in-call control bar below the table: Mute / Camera / Share / Marker / Leave + overflow kebab. Muted state binds the destructive coral treatment to the Mute button; Leave is also destructive (coral border + text); everything else is Secondary. Tight 8px spacing keeps the bar dense without crowding. * Slim 32px status bar at the bottom: control-surface URL on the left (cyan dot indicator), keyboard-shortcut hints on the right in tertiary mono. Replaces the WPF host's six-column footer. Implementation notes: * MockParticipant model populates the table with representative data (Maya / Daniel / Aicha / Sam, one as active speaker) until the ParticipantViewModel binding migrates over from the WPF host. * Custom Program.cs takes ownership of Main from the XAML compiler (DISABLE_XAML_GENERATED_MAIN). Calls Bootstrap.TryInitialize(0x00010006) before Application.Start so the unpackaged .exe can locate the WindowsAppSDK 1.6 framework MSIX at launch. Shutdown is paired in a finally block. * Theme toggle in code-behind flips Window.Content.RequestedTheme between Dark and Light. {ThemeResource} bindings auto-swap across the visual tree; system title-bar buttons (outside the XAML tree) get color updates inline so they stay readable in both modes. * app.manifest deferred from build - the framework-emitted manifest covers DPI awareness and supportedOS GUIDs; reintroducing our own goes in the next commit alongside the bootstrapper hardening. Known issue: the unpackaged .exe currently fails to activate on this build host with 'this application could not be started' before Main runs. Build is clean; published output runs the same way. Diagnosing the activation failure is the next session's first task (likely the runtimeconfig.json including Microsoft.WindowsDesktop.App which WinUI 3 doesn't want, or a missing CRT redistributable). The WPF host remains the running build until that's resolved. dotnet build TeamsISO.Windows.slnf -c Debug: 0 warnings, 0 errors.
2026-05-13 00:03:12 -04:00
private void OnThemeToggleClick(object sender, RoutedEventArgs e)
feat(winui3): ThemeManager service + Settings drawer + Help/About/Onboarding 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).
2026-05-13 00:13:58 -04:00
{
ThemeManager.Current.Toggle();
}
private bool _drawerOpen;
private void OnSettingsClick(object sender, RoutedEventArgs e)
{
_drawerOpen = !_drawerOpen;
SettingsDrawerHost.Visibility = _drawerOpen
? Visibility.Visible
: Visibility.Collapsed;
if (_drawerOpen)
{
SettingsDrawerHost.CloseRequested -= OnDrawerCloseRequested;
SettingsDrawerHost.CloseRequested += OnDrawerCloseRequested;
}
}
private void OnDrawerCloseRequested(object? sender, System.EventArgs e)
{
_drawerOpen = false;
SettingsDrawerHost.Visibility = Visibility.Collapsed;
}
feat(winui3): wire Teams orchestration into the in-call bar + rail buttons 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).
2026-05-13 21:31:04 -04:00
/// <summary>
/// Teams orchestration — mute. Drives the Teams app's in-call mute button
/// via UIAutomation (TeamsControlBridge does the localized-name search).
/// Surface failures via the status bar so the operator gets feedback
/// without a popup.
/// </summary>
private void OnMuteClick(object sender, RoutedEventArgs e)
{
var result = Services.TeamsControlBridge.ToggleMute();
StatusBarText.Text = DescribeBridgeResult("Mute", result);
}
private void OnCameraClick(object sender, RoutedEventArgs e)
{
var result = Services.TeamsControlBridge.ToggleCamera();
StatusBarText.Text = DescribeBridgeResult("Camera", result);
}
private void OnShareClick(object sender, RoutedEventArgs e)
{
var result = Services.TeamsControlBridge.OpenShareTray();
StatusBarText.Text = DescribeBridgeResult("Share", result);
}
private void OnLeaveClick(object sender, RoutedEventArgs e)
{
var result = Services.TeamsControlBridge.LeaveCall();
StatusBarText.Text = DescribeBridgeResult("Leave", result);
}
private static string DescribeBridgeResult(string action, Services.TeamsControlBridge.InvokeResult r) =>
r switch
{
Services.TeamsControlBridge.InvokeResult.Invoked => $"{action} invoked",
Services.TeamsControlBridge.InvokeResult.TeamsNotRunning => $"{action} failed — Teams isn't running",
Services.TeamsControlBridge.InvokeResult.ControlNotFound => $"{action} failed — control not visible (not in a call?)",
Services.TeamsControlBridge.InvokeResult.InvokeFailed => $"{action} failed — Teams refused the invoke",
_ => $"{action} failed",
};
/// <summary>Launch Teams if not running, else show its windows.</summary>
private void OnLaunchTeamsClick(object sender, RoutedEventArgs e)
{
if (Services.TeamsLauncher.IsRunning())
{
var shown = Services.TeamsLauncher.ShowWindows();
StatusBarText.Text = $"Showed {shown} Teams window{(shown == 1 ? "" : "s")}";
}
else
{
if (Services.TeamsLauncher.TryLaunch(out var err))
{
StatusBarText.Text = "Teams launched";
}
else
{
StatusBarText.Text = $"Teams launch failed: {err}";
}
}
}
/// <summary>Toggle Teams window visibility — invisible/visible flip.</summary>
private bool _teamsHidden;
private void OnToggleTeamsWindowsClick(object sender, RoutedEventArgs e)
{
if (_teamsHidden)
{
var n = Services.TeamsLauncher.ShowWindows();
_teamsHidden = false;
StatusBarText.Text = $"Showed {n} Teams window{(n == 1 ? "" : "s")}";
}
else
{
var n = Services.TeamsLauncher.HideWindows();
_teamsHidden = true;
StatusBarText.Text = $"Hid {n} Teams window{(n == 1 ? "" : "s")}";
}
}
feat(winui3): ThemeManager service + Settings drawer + Help/About/Onboarding 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).
2026-05-13 00:13:58 -04:00
private void ApplyResolvedTheme(ElementTheme theme)
feat(winui3): redesigned MainWindow + custom title bar + theme toggle Lands the approved shape brief as the WinUI 3 MainWindow: * 64px left rail with brand mark, primary nav (participants), Teams launch / hide / settings buttons, and the engine-status puck at the bottom. All five rail buttons use Segoe Fluent Icons glyphs at a uniform 20px optical size; no more bespoke <Path Data> shapes with inconsistent stroke weights. * 44px custom title bar via ExtendsContentIntoTitleBar + SetTitleBar(AppTitleBar). The drag region absorbs the three live-state pills inline (session timer 'live * 00:14:32', REC count + elapsed, disk free) and a slim sun/moon theme-toggle button to the left of the system Min/Max/Close controls. System buttons inherit ButtonForeground Color etc. from AppWindow.TitleBar so they match palette in both themes. * Section header with 'Participants * count' display, filter input, Refresh + Presets (Secondary buttons), and 'Enable all online' as the single cyan Primary button - finally a real button hierarchy instead of seven indistinguishable ghost buttons. * Participants list rendered as ItemsRepeater + DataTemplate for now; the CommunityToolkit DataGrid migration follows in a separate commit. Row template at 64px height with: 3px cyan left border for active speaker, avatar with initials in cyan-muted circle, name + codec line, signal lock state with dot, audio meter via ProgressBar, output name in JetBrains Mono, ISO state pill (LIVE/OFF/ERROR) at right. * Conditional in-call control bar below the table: Mute / Camera / Share / Marker / Leave + overflow kebab. Muted state binds the destructive coral treatment to the Mute button; Leave is also destructive (coral border + text); everything else is Secondary. Tight 8px spacing keeps the bar dense without crowding. * Slim 32px status bar at the bottom: control-surface URL on the left (cyan dot indicator), keyboard-shortcut hints on the right in tertiary mono. Replaces the WPF host's six-column footer. Implementation notes: * MockParticipant model populates the table with representative data (Maya / Daniel / Aicha / Sam, one as active speaker) until the ParticipantViewModel binding migrates over from the WPF host. * Custom Program.cs takes ownership of Main from the XAML compiler (DISABLE_XAML_GENERATED_MAIN). Calls Bootstrap.TryInitialize(0x00010006) before Application.Start so the unpackaged .exe can locate the WindowsAppSDK 1.6 framework MSIX at launch. Shutdown is paired in a finally block. * Theme toggle in code-behind flips Window.Content.RequestedTheme between Dark and Light. {ThemeResource} bindings auto-swap across the visual tree; system title-bar buttons (outside the XAML tree) get color updates inline so they stay readable in both modes. * app.manifest deferred from build - the framework-emitted manifest covers DPI awareness and supportedOS GUIDs; reintroducing our own goes in the next commit alongside the bootstrapper hardening. Known issue: the unpackaged .exe currently fails to activate on this build host with 'this application could not be started' before Main runs. Build is clean; published output runs the same way. Diagnosing the activation failure is the next session's first task (likely the runtimeconfig.json including Microsoft.WindowsDesktop.App which WinUI 3 doesn't want, or a missing CRT redistributable). The WPF host remains the running build until that's resolved. dotnet build TeamsISO.Windows.slnf -c Debug: 0 warnings, 0 errors.
2026-05-13 00:03:12 -04:00
{
if (Content is FrameworkElement root)
{
feat(winui3): ThemeManager service + Settings drawer + Help/About/Onboarding 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).
2026-05-13 00:13:58 -04:00
root.RequestedTheme = theme;
feat(winui3): redesigned MainWindow + custom title bar + theme toggle Lands the approved shape brief as the WinUI 3 MainWindow: * 64px left rail with brand mark, primary nav (participants), Teams launch / hide / settings buttons, and the engine-status puck at the bottom. All five rail buttons use Segoe Fluent Icons glyphs at a uniform 20px optical size; no more bespoke <Path Data> shapes with inconsistent stroke weights. * 44px custom title bar via ExtendsContentIntoTitleBar + SetTitleBar(AppTitleBar). The drag region absorbs the three live-state pills inline (session timer 'live * 00:14:32', REC count + elapsed, disk free) and a slim sun/moon theme-toggle button to the left of the system Min/Max/Close controls. System buttons inherit ButtonForeground Color etc. from AppWindow.TitleBar so they match palette in both themes. * Section header with 'Participants * count' display, filter input, Refresh + Presets (Secondary buttons), and 'Enable all online' as the single cyan Primary button - finally a real button hierarchy instead of seven indistinguishable ghost buttons. * Participants list rendered as ItemsRepeater + DataTemplate for now; the CommunityToolkit DataGrid migration follows in a separate commit. Row template at 64px height with: 3px cyan left border for active speaker, avatar with initials in cyan-muted circle, name + codec line, signal lock state with dot, audio meter via ProgressBar, output name in JetBrains Mono, ISO state pill (LIVE/OFF/ERROR) at right. * Conditional in-call control bar below the table: Mute / Camera / Share / Marker / Leave + overflow kebab. Muted state binds the destructive coral treatment to the Mute button; Leave is also destructive (coral border + text); everything else is Secondary. Tight 8px spacing keeps the bar dense without crowding. * Slim 32px status bar at the bottom: control-surface URL on the left (cyan dot indicator), keyboard-shortcut hints on the right in tertiary mono. Replaces the WPF host's six-column footer. Implementation notes: * MockParticipant model populates the table with representative data (Maya / Daniel / Aicha / Sam, one as active speaker) until the ParticipantViewModel binding migrates over from the WPF host. * Custom Program.cs takes ownership of Main from the XAML compiler (DISABLE_XAML_GENERATED_MAIN). Calls Bootstrap.TryInitialize(0x00010006) before Application.Start so the unpackaged .exe can locate the WindowsAppSDK 1.6 framework MSIX at launch. Shutdown is paired in a finally block. * Theme toggle in code-behind flips Window.Content.RequestedTheme between Dark and Light. {ThemeResource} bindings auto-swap across the visual tree; system title-bar buttons (outside the XAML tree) get color updates inline so they stay readable in both modes. * app.manifest deferred from build - the framework-emitted manifest covers DPI awareness and supportedOS GUIDs; reintroducing our own goes in the next commit alongside the bootstrapper hardening. Known issue: the unpackaged .exe currently fails to activate on this build host with 'this application could not be started' before Main runs. Build is clean; published output runs the same way. Diagnosing the activation failure is the next session's first task (likely the runtimeconfig.json including Microsoft.WindowsDesktop.App which WinUI 3 doesn't want, or a missing CRT redistributable). The WPF host remains the running build until that's resolved. dotnet build TeamsISO.Windows.slnf -c Debug: 0 warnings, 0 errors.
2026-05-13 00:03:12 -04:00
}
feat(winui3): ThemeManager service + Settings drawer + Help/About/Onboarding 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).
2026-05-13 00:13:58 -04:00
AppWindow.TitleBar.ButtonForegroundColor = ThemeManager.TitleBarForegroundFor(theme);
AppWindow.TitleBar.ButtonHoverBackgroundColor = ThemeManager.TitleBarHoverBgFor(theme);
AppWindow.TitleBar.ButtonPressedBackgroundColor = ThemeManager.TitleBarHoverBgFor(theme);
feat(winui3): engine wired — discovers Teams participants live The WinUI 3 host now stands up the full engine pipeline on launch and discovers participants from the operator's live Teams meeting. Verified end-to-end against a real call: window opened, NDI runtime preflight passed, IsoController spun up, and Participants observable yielded three live entries ((Local), Active Speaker, Brendon Power) with their TEAMSISO_<name> output names in the redesigned shell. What this commit lands: ViewModels/ (slim ports of the WPF host's view-models — engine layer shared verbatim via ProjectReference): * ObservableObject.cs — INPC base, mirrors the WPF version * RelayCommand.cs — sync + typed + async variants; ICommand is the same shared type across both hosts (System.ObjectModel.dll) * ParticipantViewModel.cs — DisplayName / Initials / SourceCodec / IsoStateLabel / DisplayedAudioLevel / IsActiveSpeaker / IsOnline / OutputName + ToggleIsoCommand. Drops the WPF-specific thumbnail WriteableBitmap path, clipboard, PreviewWindow, snapshot encoder — those come back when the WinUI imaging pipeline is wired (Phase 5 of the migration plan). * MainViewModel.cs — subscribes to IIsoController.Participants on a DispatcherQueueSynchronizationContext, owns the ObservableCollection, runs a 1Hz DispatcherQueueTimer for stats + active-speaker highlight + session-elapsed text. Commands: EnableAllOnline, StopAllIsos, RefreshDiscovery, DropRecordingMarker, ToggleByIndex. App.xaml.cs: * OnLaunched brings up MainWindow first, then fires WireEngineAsync so the user sees the shell immediately while NDI preflight + engine setup proceed. * Full pipeline: EngineLogging → NdiInteropPInvoke (with friendly fallback message if the NDI runtime isn't installed) → ConfigStore at %APPDATA%\TeamsISO\config.json → NdiRuntimeProbe + scaler → IsoPipeline factory → IsoController → MainViewModel → MainWindow.AttachViewModel → IsoController.StartAsync. * Logger writes to the same %LOCALAPPDATA%\TeamsISO\Logs as the WPF host so a mixed-host operator sees a single timeline. MainWindow.xaml + .xaml.cs: * x:Name on the section header buttons (RefreshButton, StopAllButton, EnableAllButton, MarkerButton) and on the status bar text + the ParticipantsHost grid. * AttachViewModel wires those buttons to view-model commands; pushes StatusText + ParticipantCountText through PropertyChanged. * BuildSimpleRow imperatively constructs each row (Grid with name + codec + output + ISO toggle pill) instead of going through a DataTemplate. Rationale: declaring a DataTemplate in Grid.Resources OR loading one via XamlReader.Load both crash WinUI 3's XAML parser at runtime on this build host (same HR=0x802b000a we saw with the SettingsDrawer NavigationView). Imperative construction sidesteps the parser. The rich row template (avatar circle, audio meter, active-speaker accent) returns in Phase 5 alongside the CommunityToolkit DataGrid swap. * Per-row PropertyChanged subscriptions refresh DisplayName, codec, output name, and ISO pill text as the engine pushes updates. Verified live (PID 4824, 2026-05-13 08:02): three real Teams participants appeared in the redesigned shell within seconds of launch, status bar populated with "3 participants · 0 routing", section header showed "Participants 3", and the title-bar live pills rendered in their proper places (placeholder values for now; binding to SessionElapsed / IsRecording lands in the next commit).
2026-05-13 08:03:32 -04:00
ThemeToggleIcon.Glyph = theme == ElementTheme.Light ? "" : "";
feat(winui3): scaffold TeamsISO.App.WinUI alongside the WPF host First step of the WinUI 3 replatform per the approved redesign brief. The new project coexists with the existing src/TeamsISO.App (WPF) so the WPF host keeps building and shipping while the WinUI 3 redesign lands incrementally. Once the WinUI 3 build is feature-complete and tested against a real Teams meeting, the WPF project is retired. Scaffold contents: * src/TeamsISO.App.WinUI/TeamsISO.App.WinUI.csproj Windows App SDK 1.6 LTS (250602001), unpackaged mode (WindowsPackageType=None) so the existing MSI installer keeps working. Target framework net8.0-windows10.0.19041.0, min platform 10.0.17763.0 to preserve Win10 1809+ compatibility for working broadcast hardware. Pins WindowsSdkPackageVersion=10.0.19041.38 so .NET SDK 8.0.301 builds cleanly without an SDK upgrade on the build host. * src/TeamsISO.App.WinUI/app.manifest PerMonitorV2 DPI awareness + gdiScaling for crisp text on high-DPI broadcast monitors. asInvoker trust level (control surface :9755 and OSC :9000 bind to 127.0.0.1, no admin needed). * App.xaml + App.xaml.cs Minimal startup: brings up MainWindow. The full pipeline (NDI runtime preflight, IsoController wiring, single-instance mutex, REST + OSC bridge, tray icon, crash diagnostics, auto-update banner, onboarding) migrates in subsequent commits. * Themes/Tokens.xaml Wild Dragon design tokens as ThemeDictionary entries (Default = Dark, Light). Colors as Color resources, Brushes paired per theme so {ThemeResource} auto-swaps when RequestedTheme flips — no app restart, no flicker. Spacing/radii/typography tokens are theme-agnostic at the outer level. Light palette maintains brand recognition via cyan-tinted off-whites (#FAFAFB canvas, #F0F1F3 rail) rather than pure white, and splits cyan into accent.cyan.surface (#97EDF0, works in both modes because text on top is near-black) and accent.cyan.text (#97EDF0 dark / #0E7C82 light) so captions and inline labels keep AA contrast. * Themes/Controls.xaml Button hierarchy with real commitments: Primary (cyan fill, one per surface), Secondary (transparent bordered), Tertiary (text only), Destructive (coral border + text), Caption (titlebar), RailIcon. Typographic ramp (Display / Title / Heading / Body / Subtle / Caption / Mono) at the DESIGN.md 1.25 ratio. * CommunityToolkit.WinUI.UI.Controls.DataGrid 7.1.2 referenced for the participants table migration. (Toolkit 8.x dropped DataGrid; 7.x is the only currently-maintained free option for WinUI 3.) * Inter.ttf + JetBrainsMono.ttf + dragon-mark.png + teamsiso.ico copied from the WPF project's Assets/ so the WinUI 3 host is self-contained. * TeamsISO.sln + TeamsISO.Windows.slnf updated to include the new project. The .slnf paths switch to backslash form so MSBuild can match them against the .sln's canonical path representation. Verified: dotnet build TeamsISO.Windows.slnf -c Debug succeeds with 0 warnings and 0 errors for all 8 projects (WPF host, WinUI 3 host, engine, NDI interop, console, three test projects).
2026-05-12 23:52:35 -04:00
}
feat(winui3): engine wired — discovers Teams participants live The WinUI 3 host now stands up the full engine pipeline on launch and discovers participants from the operator's live Teams meeting. Verified end-to-end against a real call: window opened, NDI runtime preflight passed, IsoController spun up, and Participants observable yielded three live entries ((Local), Active Speaker, Brendon Power) with their TEAMSISO_<name> output names in the redesigned shell. What this commit lands: ViewModels/ (slim ports of the WPF host's view-models — engine layer shared verbatim via ProjectReference): * ObservableObject.cs — INPC base, mirrors the WPF version * RelayCommand.cs — sync + typed + async variants; ICommand is the same shared type across both hosts (System.ObjectModel.dll) * ParticipantViewModel.cs — DisplayName / Initials / SourceCodec / IsoStateLabel / DisplayedAudioLevel / IsActiveSpeaker / IsOnline / OutputName + ToggleIsoCommand. Drops the WPF-specific thumbnail WriteableBitmap path, clipboard, PreviewWindow, snapshot encoder — those come back when the WinUI imaging pipeline is wired (Phase 5 of the migration plan). * MainViewModel.cs — subscribes to IIsoController.Participants on a DispatcherQueueSynchronizationContext, owns the ObservableCollection, runs a 1Hz DispatcherQueueTimer for stats + active-speaker highlight + session-elapsed text. Commands: EnableAllOnline, StopAllIsos, RefreshDiscovery, DropRecordingMarker, ToggleByIndex. App.xaml.cs: * OnLaunched brings up MainWindow first, then fires WireEngineAsync so the user sees the shell immediately while NDI preflight + engine setup proceed. * Full pipeline: EngineLogging → NdiInteropPInvoke (with friendly fallback message if the NDI runtime isn't installed) → ConfigStore at %APPDATA%\TeamsISO\config.json → NdiRuntimeProbe + scaler → IsoPipeline factory → IsoController → MainViewModel → MainWindow.AttachViewModel → IsoController.StartAsync. * Logger writes to the same %LOCALAPPDATA%\TeamsISO\Logs as the WPF host so a mixed-host operator sees a single timeline. MainWindow.xaml + .xaml.cs: * x:Name on the section header buttons (RefreshButton, StopAllButton, EnableAllButton, MarkerButton) and on the status bar text + the ParticipantsHost grid. * AttachViewModel wires those buttons to view-model commands; pushes StatusText + ParticipantCountText through PropertyChanged. * BuildSimpleRow imperatively constructs each row (Grid with name + codec + output + ISO toggle pill) instead of going through a DataTemplate. Rationale: declaring a DataTemplate in Grid.Resources OR loading one via XamlReader.Load both crash WinUI 3's XAML parser at runtime on this build host (same HR=0x802b000a we saw with the SettingsDrawer NavigationView). Imperative construction sidesteps the parser. The rich row template (avatar circle, audio meter, active-speaker accent) returns in Phase 5 alongside the CommunityToolkit DataGrid swap. * Per-row PropertyChanged subscriptions refresh DisplayName, codec, output name, and ISO pill text as the engine pushes updates. Verified live (PID 4824, 2026-05-13 08:02): three real Teams participants appeared in the redesigned shell within seconds of launch, status bar populated with "3 participants · 0 routing", section header showed "Participants 3", and the title-bar live pills rendered in their proper places (placeholder values for now; binding to SessionElapsed / IsRecording lands in the next commit).
2026-05-13 08:03:32 -04:00
feat(winui3): scaffold TeamsISO.App.WinUI alongside the WPF host First step of the WinUI 3 replatform per the approved redesign brief. The new project coexists with the existing src/TeamsISO.App (WPF) so the WPF host keeps building and shipping while the WinUI 3 redesign lands incrementally. Once the WinUI 3 build is feature-complete and tested against a real Teams meeting, the WPF project is retired. Scaffold contents: * src/TeamsISO.App.WinUI/TeamsISO.App.WinUI.csproj Windows App SDK 1.6 LTS (250602001), unpackaged mode (WindowsPackageType=None) so the existing MSI installer keeps working. Target framework net8.0-windows10.0.19041.0, min platform 10.0.17763.0 to preserve Win10 1809+ compatibility for working broadcast hardware. Pins WindowsSdkPackageVersion=10.0.19041.38 so .NET SDK 8.0.301 builds cleanly without an SDK upgrade on the build host. * src/TeamsISO.App.WinUI/app.manifest PerMonitorV2 DPI awareness + gdiScaling for crisp text on high-DPI broadcast monitors. asInvoker trust level (control surface :9755 and OSC :9000 bind to 127.0.0.1, no admin needed). * App.xaml + App.xaml.cs Minimal startup: brings up MainWindow. The full pipeline (NDI runtime preflight, IsoController wiring, single-instance mutex, REST + OSC bridge, tray icon, crash diagnostics, auto-update banner, onboarding) migrates in subsequent commits. * Themes/Tokens.xaml Wild Dragon design tokens as ThemeDictionary entries (Default = Dark, Light). Colors as Color resources, Brushes paired per theme so {ThemeResource} auto-swaps when RequestedTheme flips — no app restart, no flicker. Spacing/radii/typography tokens are theme-agnostic at the outer level. Light palette maintains brand recognition via cyan-tinted off-whites (#FAFAFB canvas, #F0F1F3 rail) rather than pure white, and splits cyan into accent.cyan.surface (#97EDF0, works in both modes because text on top is near-black) and accent.cyan.text (#97EDF0 dark / #0E7C82 light) so captions and inline labels keep AA contrast. * Themes/Controls.xaml Button hierarchy with real commitments: Primary (cyan fill, one per surface), Secondary (transparent bordered), Tertiary (text only), Destructive (coral border + text), Caption (titlebar), RailIcon. Typographic ramp (Display / Title / Heading / Body / Subtle / Caption / Mono) at the DESIGN.md 1.25 ratio. * CommunityToolkit.WinUI.UI.Controls.DataGrid 7.1.2 referenced for the participants table migration. (Toolkit 8.x dropped DataGrid; 7.x is the only currently-maintained free option for WinUI 3.) * Inter.ttf + JetBrainsMono.ttf + dragon-mark.png + teamsiso.ico copied from the WPF project's Assets/ so the WinUI 3 host is self-contained. * TeamsISO.sln + TeamsISO.Windows.slnf updated to include the new project. The .slnf paths switch to backslash form so MSBuild can match them against the .sln's canonical path representation. Verified: dotnet build TeamsISO.Windows.slnf -c Debug succeeds with 0 warnings and 0 errors for all 8 projects (WPF host, WinUI 3 host, engine, NDI interop, console, three test projects).
2026-05-12 23:52:35 -04:00
}