Commit graph

167 commits

Author SHA1 Message Date
25229bdf1b ISO toggle: square corners to match the rest of the button family
Some checks failed
CI / build-and-test (push) Failing after 27s
Wd.Button.IsoToggle was the only button in the GUI using CornerRadius=999
(full pill). It read as a different control type from the toolbar buttons
around it (Enable all, Refresh, Presets, Stop all, Mute, Cam, Leave —
all Radius.M). The pill shape was meant to make the LIVE state visually
distinct, but the status-coded fill (cyan/coral/amber) already carries
that signal — the geometry was double-duty.

Swap the IsoToggle's CornerRadius from 999 to Radius.M so every button
in the app shares the same shape language. Status read remains via the
fill color.
2026-05-15 16:19:19 -04:00
29f7e56eb9 gear icon: swap Path glyph for U+2699 + bump column to 56px
The custom Path gear with Stroke=Wd.Text.Secondary + StrokeThickness=1.4
rendered as a near-invisible thin grey shape against the dark row
background — users couldn't tell the column was clickable.

Replace with TextBlock rendering U+2699 GEAR from Segoe UI Symbol
at 16px and Wd.Text.Primary foreground. Universally recognized as
'settings', renders crisply at any DPI, and stands out against the
row. Header bumped from empty to 'CFG' so the affordance is
discoverable, column widened from 32px to 56px so 'CFG' fits cleanly.
2026-05-15 16:11:53 -04:00
5a43c9cb6a feat: per-ISO framerate/resolution/aspect/audio overrides + thumbnail BMP
Some checks failed
CI / build-and-test (push) Failing after 30s
Engine: IsoAssignment record gets optional Override (FrameProcessingSettings?). IsoController hydrates _overrides dict from config.json on startup, uses override at EnableIsoAsync, persists with assignment, exposes GetIsoOverride + SetIsoOverrideAsync. SetIsoOverrideAsync hot-swaps a running pipeline (Disable + 150ms delay + Enable) when the override changes.

REST: POST /participants/{id}/override (body: framerate/resolution/aspect/audio enum strings, all optional, missing fall back to globals); DELETE /participants/{id}/override clears. GET /participants now includes per-row effective {framerate, resolution, aspect, audio, isOverride} plus top-level globals block.

Web /ui: per-card collapsible override panel with four selects + Apply / Clear. OVR pill + cyan inset edge mark overridden rows. Open-panel state survives WS re-renders.

Desktop: per-row gear column in the v2 DataGrid opens IsoOverrideDialog (420x360) with four combos. Clear button removes the override.

Thumbnail endpoint switched from WPF JpegBitmapEncoder (NREs from non-UI HttpListener threads) to pure-managed 32bpp BMP encoder. Nearest-neighbor downscale to 192-wide. /participants/{id}/thumbnail.bmp; legacy .jpg URL still works.

Known limitation: ParticipantTracker regenerates IDs for display-name-keyed participants across process restarts, orphaning the persisted override. Override works within a session; cross-restart persistence is best-effort until the tracker is taught to use stable keys. Filed as task 43.
2026-05-15 15:31:32 -04:00
647deec304 feat(web): topology + thumbnail endpoints, redesigned /ui control panel
Some checks failed
CI / build-and-test (push) Failing after 30s
REST additions: GET /topology returns mode (hidden/public/unknown) + sender/receiver group lists. POST /topology/apply confines local senders to teamsiso-input + receivers to public+teamsiso-input. POST /topology/restore returns both to public defaults.

GET /participants/{id}/thumbnail.jpg encodes the latest engine ProcessedFrame as a 192-wide JPEG. 404 when no pipeline is running. Used by the /ui control panel for live preview tiles.

Settings: ControlSurfaceEnabled now persists across sessions via UIPreferences and auto-starts the server on app launch when previously enabled.

/ui control panel rebuilt: live thumbnail per row, topology toggle card with Hide/Restore buttons, removed dead recording marker button, larger layout (920px), participant rows in single card with hover affordances.
2026-05-15 15:06:11 -04:00
4944de5feb feat(wpf): v2 - restore live thumbnail preview column in participants table
96x54 thumbnail (16:9) fed from the engine's most recent ProcessedFrame. Em-dash placeholder when no pipeline is running. Same pattern as v1 - lifted Image binding to Thumbnail with HasThumbnail visibility flip. Sits between the state LED and the name+codec caption.
2026-05-15 14:33:00 -04:00
209b643cd5 fix(wpf): MainViewModel subscription via direct Subscribe + Dispatcher marshal
The .ObserveOn(SynchronizationContextScheduler(SyncContext.Current)) path captured a synchronization context at subscribe time that didn't pump subsequent OnNext emissions in WPF startup, leaving the Participants collection empty even though the engine's discovery was firing. Console probe confirmed engine sees Teams sources; only the GUI consumer was broken.

Switched to direct Subscribe + Dispatcher.InvokeAsync inside the callback (same pattern proven by Console.Program.cs). Subscribe-time context capture is gone; every emission marshals to the UI thread on its own.
2026-05-15 14:27:17 -04:00
d282e1b0f8 feat(wpf): v2 task 39+40 - studio table redesign + Ctrl+K command palette
Some checks failed
CI / build-and-test (push) Failing after 30s
Task 39: 5-column participants table - state LED, name+codec caption, 5-bar audio meter, mono output name, ISO pill. Row height 52, full-row active-speaker tint (no left stripe). New converter LevelThresholdConverter, OutputName property on ParticipantViewModel.

Task 40: Ctrl+K / Ctrl+P command palette - chromeless centered floating window, fuzzy Contains match across Label/Category/Keywords, arrow nav, Enter invoke, Esc close. Quick/Teams/Network/App categories cover top operator verbs and theme switching.

Also: log startup exceptions to Serilog before the modal MessageBox fires - much better triage signal than user-pasted dialog text.
2026-05-15 11:15:00 -04:00
c27130302f feat(wpf): v2 'Studio Terminal' shell - theme system, header, transport strip, drawer
Some checks failed
CI / build-and-test (push) Failing after 31s
- Theme split: Theme.Dark.xaml + Theme.Light.xaml + ThemeManager

- New shell: 32px header (mark + wordmark + 3 icons), 40px transport strip, conditional meeting bar, slide-over settings drawer

- Removed: 72px rail, 380px permanent settings panel, 6-column footer, custom chromeless title bar buttons

- Ctrl+T toggles theme; follows Windows app-mode by default

- Shape doc at docs/shapes/2026-05-13-teamsiso-v2-studio-terminal.md
2026-05-14 12:46:24 -04:00
1d1ce6a2a0 feat(wpf): rollback to WPF host, axe recording, fix settings pane
Some checks failed
CI / build-and-test (push) Failing after 29s
2026-05-14 06:02:40 -04:00
426cf33dec docs(preview): screenshot — engine-wired + colored ISO pills
Some checks failed
CI / build-and-test (push) Failing after 26s
2026-05-13 21:37:52 -04:00
9ae14c8ee9 feat(winui3): colored ISO pills + active-speaker accent on rows
Some checks failed
CI / build-and-test (push) Has been cancelled
Two visual upgrades on the participant rows:

  * The ISO state pill now flips background / border / text colors
    based on the engine's reported state — green for LIVE, coral for
    ERROR, amber for STARTING / NO SIGNAL, neutral surface for OFF.
    Brushes pulled from the ThemeResource ramp (StatusLiveBg /
    StatusLive / AccentCoralBg / AccentCoral / StatusWarnBg /
    StatusWarn / BgSurface / BorderStrong). Mirrors the WPF host's
    IsoToggle data-trigger behavior but built imperatively.
  * The active-speaker left accent — a 3px cyan border at the row's
    left edge — appears when MainViewModel's 1Hz stats tick marks the
    loudest participant. Hidden by default; flips Visibility on
    PropertyChanged(IsActiveSpeaker).

Row layout extended to accommodate: column 0 = 3px accent strip,
column 1 = 20px spacer, column 2 = name + codec (1*), column 3 =
output name (140px fixed), column 4 = ISO pill (auto).

ApplyIsoPillStyling is the brush-mapping helper — called once at row
construct and again on every IsoStateLabel / IsEnabled change. The
brush keys all resolve via Application.Current.Resources rather than
ThemeResource markup since the row is constructed imperatively (no
XAML to apply ThemeResource markup against).

Verified end-to-end: dotnet build clean, app launches with 3 live
participants in the row, all pills showing OFF (neutral surface +
strong border). Once a participant goes through OFF → STARTING (amber)
→ LIVE (green), the pill colors will update on the 1Hz stats tick.
2026-05-13 21:37:36 -04:00
f7249c31c2 feat(winui3): persist theme preference to UIPreferences
Some checks failed
CI / build-and-test (push) Failing after 26s
Services/UIPreferences.cs — mirror of the WPF host's UIPreferences,
sharing %LOCALAPPDATA%\TeamsISO\ui-prefs.json on disk. Adds a Theme
field ("System" / "Dark" / "Light") that the WPF host's UIPreferences
will pick up when its theme system lands (JSON deserialization is
forward-compatible — extra fields are ignored, missing fields fall
back to defaults).

ThemeManager hydration:
  * Constructor reads UIPreferences.Theme on first .Current access.
  * Defaults to "System" when the file is missing, the value is
    invalid, or load throws (defensive — ThemeManager.Current is a
    static singleton, a throw would break theme resolution app-wide).
ThemeManager.Set persistence:
  * Calls UIPreferences.SetTheme(preference) which does a read-modify-
    write of the JSON (so other fields aren't trampled).
  * Persistence is best-effort wrapped in try/catch — disk full,
    permission denied, etc. fall through and the in-memory state still
    holds for the session.

End-to-end now: title-bar sun/moon toggle → ThemeManager.Toggle →
.Set("Dark"/"Light") → JSON write → next launch reads the preference
and applies before the first frame. Operator's theme choice survives
across launches and across host swaps once the WPF host learns the
field.
2026-05-13 21:35:31 -04:00
7c269f2c40 feat(winui3): keyboard shortcuts (F1, Ctrl+M, Ctrl+Shift+S, Ctrl+R, 1-9, Esc)
Some checks failed
CI / build-and-test (push) Failing after 27s
Adds the operator's shortcut surface to the WinUI 3 host via
KeyboardAccelerator attached to the window's content root:

  * F1 — open the keyboard-cheat-sheet HelpDialog as a ContentDialog.
  * Ctrl+M — drop a recording marker (invokes
    MainViewModel.DropRecordingMarkerCommand, which fans out to every
    active recorder via IIsoController.AddRecordingMarker).
  * Ctrl+Shift+S — panic stop (invokes StopAllIsosCommand).
  * Ctrl+R — refresh NDI discovery.
  * 1-9 + NumPad 1-9 — toggle ISO for the Nth visible participant
    (invokes ToggleByIndexCommand with the digit as the parameter).
  * Esc — dismiss the settings drawer when open.

Mirrors the WPF host's <Window.InputBindings> verbatim so the
operator's muscle memory transfers across hosts.

Wire-up note: WinUI 3 KeyboardAccelerator uses
TypedEventHandler<KeyboardAccelerator, KeyboardAcceleratorInvokedEvent
Args>, not System.EventHandler<T> like XAML islands suggest in some
docs. The Bind local fn takes the correct type explicitly so the
compiler doesn't trip on the conversion.

Verified: dotnet build clean, app launches and accelerators register
without crashing the XAML parser.
2026-05-13 21:33:02 -04:00
7ac56c2661 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
538dd98f54 docs(preview): proof screenshot — WinUI 3 host vs live Teams meeting
Some checks failed
CI / build-and-test (push) Failing after 29s
docs/preview/winui3-engine-wired-with-live-teams.png — fullscreen
capture of the redesigned WinUI 3 TeamsISO running against the live
Teams meeting on the build host. Shows:

  * Three participants discovered from the live meeting — (Local),
    Active Speaker, Brendon Power — each with their TEAMSISO_<name>
    output name in the redesigned shell at the right column.
  * Section header reads "Participants 3" with the live count badge.
  * Status bar reads "3 participants · 0 routing".
  * Windows Security / Firewall dialog asking permission for TeamsISO
    to access public + private networks. This appeared the moment the
    operator clicked "Enable all online" — which proves the click was
    wired to MainViewModel.EnableAllOnlineCommand which fanned out to
    each ParticipantViewModel.ToggleIsoCommand which awaited
    IsoController.EnableIsoAsync, which spun up an NDI sender, which
    Windows Firewall intercepted on first launch.
  * Bottom row: the in-call control bar with Muted / Camera / Share /
    Marker / Leave — visible alongside the participants area as
    designed.

So the engine wiring is verified end-to-end:

    click on cyan "Enable all online" CTA
      → MainViewModel.EnableAllOnlineCommand
        → ParticipantViewModel.ToggleIsoCommand for each online row
          → IIsoController.EnableIsoAsync(id, outputName)
            → IsoPipeline.StartAsync
              → NdiInteropPInvoke.CreateSender
                → bind to NDI port → Windows Firewall prompt

Next session: the user clicks Allow on the firewall prompt (once,
remembered for subsequent runs), and the ISO pills will transition
from OFF → STARTING → LIVE as each NDI sender comes online.
2026-05-13 08:12:36 -04:00
83c954d80d feat(winui3): engine wired — discovers Teams participants live
Some checks failed
CI / build-and-test (push) Failing after 27s
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
4ec28adbd9 docs(work-log): add the SettingsDrawer victory to the commit table
Some checks failed
CI / build-and-test (push) Failing after 29s
2026-05-13 00:51:41 -04:00
a05c0a75d2 feat(winui3): SettingsDrawer hosts successfully — NavigationView swap
Some checks failed
CI / build-and-test (push) Failing after 29s
Replaces the NavigationView in SettingsDrawer with a simpler
StackPanel of tab buttons built imperatively at runtime. The
NavigationView's resource-dictionary expansion (or its default
template loading) was crashing the XAML parser at SettingsDrawer's
InitializeComponent on WinUI 3 1.8.

New shape:

- `TabStrip` StackPanel populated in BuildTabStrip() with five
  Tertiary-styled Button instances. Selection updates the foreground
  to AccentCyanText for the active tab and FgSecondary for the rest.
- `TabContent` ScrollViewer remains; RebuildTabContent(key) clears
  and rebuilds via the same helpers as before (SettingHeader,
  SettingRow, SettingNote, AccentSwatch).
- Each tab's content moved into its own helper method
  (BuildAppearanceTab / Routing / Display / Control / Advanced) so
  the switch in the old OnTabSelectionChanged disappears.

MainWindow re-hosts the drawer at Grid.Row=0, RowSpan=4, right-
aligned, 400px wide, Visibility=Collapsed. OnSettingsClick toggles
visibility. Verified: dotnet build + run launches cleanly, and the
window stays alive (PID confirmed via Get-Process).

This closes Phase 6 (secondary windows) for the drawer specifically.
The Help, About, and Onboarding dialogs are ContentDialogs that
don't host inline so they should be straightforward to wire to
their respective triggers (F1 / About button / first launch) in
Phase 7.
2026-05-13 00:50:54 -04:00
27f47401d9 build(winui3): keep SettingsDrawer host deferred + narrow the suspect
Some checks failed
CI / build-and-test (push) Failing after 28s
Tried re-hosting SettingsDrawer with `Visibility="Collapsed"` (no
RenderTransform / Storyboard this time). Still crashes the XAML parser
at startup with the same HR 0x802b000a.

Narrows the suspect: the crash is inside SettingsDrawer.xaml's
InitializeComponent, not in MainWindow.xaml's hosting of it. Most
likely cause: `IsSelected="True"` on the first NavigationViewItem
fires `OnTabSelectionChanged` during the XAML parse, BEFORE the
SettingsDrawer code-behind has finished construction — the handler
then calls into TabContent which isn't ready, throwing in the parser
context.

Two fixes to try next session:

1. Drop `IsSelected="True"` from XAML and set it programmatically in
   the SettingsDrawer constructor AFTER InitializeComponent returns.
2. Verify the OnTabSelectionChanged signature for WinUI 3 1.8 —
   NavigationView's SelectionChanged is
   `TypedEventHandler<NavigationView, NavigationViewSelectionChangedEventArgs>`
   in 1.8 (might be different from the 1.6 SDK signature I wrote
   against).

For now, the MainWindow's OnSettingsClick is a no-op stub. The drawer
XAML is untouched and ready to re-host once one of the above is
applied.

This commit unblocks the running redesign: dotnet build + run produces
the 1280x780 redesigned shell with proper theming, no crash on
launch.
2026-05-13 00:48:03 -04:00
639a7ea9f9 docs(work-log): final overnight summary — WinUI 3 host runs
Some checks failed
CI / build-and-test (push) Failing after 27s
Rewrites the TL;DR + commit list + tomorrow's first-action list to
reflect the actual end-of-session state:

- The WinUI 3 redesigned host LAUNCHES and renders correctly at
  1280x780 with proper dark/light theming.
- Two activation blockers identified and both resolved (DDLM swap +
  inline SettingsDrawer host removed).
- 18 commits pushed to origin/main.
- Phase 3 of the migration plan is closed; Phase 4 (view-model
  wiring) opens next session.

This is the closing log entry. The redesign is real and on disk.
2026-05-13 00:45:47 -04:00
eee307d711 docs(preview): proof-of-running WinUI 3 screenshots (dark + light)
Some checks failed
CI / build-and-test (push) Failing after 27s
Two screenshots captured from the live TeamsISO.App.WinUI .exe at
1280×780, one per theme. Both prove the redesign renders end-to-end
on Windows 11 with WindowsAppSDK 1.8 and no view-model wiring yet:

* docs/preview/winui3-mainwindow-light.png — App.Current.RequestedTheme
  set to Light via ThemeManager. Wild Dragon "W" mark renders as cyan
  (#0E7C82) on cyan-muted (#E6F8F9) tile per the light-mode accent
  split from DESIGN.md. All other rail icons render at FgSecondary
  (#4A4B50) for AA contrast.
* docs/preview/winui3-mainwindow-dark.png — same render, dark theme.
  Wild Dragon mark uses the airy #97EDF0 cyan on the deeper
  cyan-muted (#1B3537) tile. Rail icons + section text at FgPrimary
  (#F4F4F6).

ThemeManager default reverted to "System" (the screenshot for dark
mode was taken with the default temporarily set to "Dark", then
reverted before this commit). The light-mode screenshot is what runs
when the OS app-mode is light, which is what happened on this build
host tonight.

These are the artifacts to point at when stakeholders ask "what does
the redesign look like in practice?" — they are the WinUI 3 .exe, not
the HTML preview.
2026-05-13 00:44:32 -04:00
a33f80d345 feat(winui3): WinUI 3 host LAUNCHES — verified rendering on Windows
Some checks failed
CI / build-and-test (push) Failing after 26s
Removing the inline-hosted SettingsDrawer (and its accompanying
Storyboard resources targeting TranslateTransform.X) unblocks the
launch. The WinUI 3 host now opens, paints, and stays alive. Verified
via screenshot:

  * 64px left rail with Wild Dragon "W" brand mark + participants /
    Teams / hide-Teams / settings / engine-status puck buttons (Segoe
    Fluent Icons throughout, uniform stroke)
  * 44px custom title bar with the live pills inline (live · session
    timer · REC count · disk free) and a theme toggle to the left of
    the system min/max/close
  * Section header: "Participants 4" + filter input + Refresh +
    Presets + the single cyan Primary CTA "Enable all online"
  * Participants list placeholder ("View-model wiring queued for the
    next session") in the hero row — real DataGrid + bindings land in
    Phase 4/5 of the migration plan
  * Conditional in-call control bar: Muted (destructive coral) +
    Camera/Share/Marker (Secondary) + Leave (destructive coral) +
    overflow kebab
  * Slim status bar: control-surface URL + keyboard shortcut hints
  * Rendered in LIGHT THEME on first run (matched the OS app-mode
    setting via ThemeManager.ResolveTheme), confirming the
    ThemeDictionary swap works end-to-end

Two open suspects causing the SettingsDrawer host to crash WinUI 3's
XAML parser with HR=0x802b000a (XAML_E_PARSER_GENERAL_ERROR):

  * RenderTransform with a x:Name'd TranslateTransform — WinUI 3
    might not allow naming transforms inside RenderTransform the way
    WPF does
  * Storyboard.TargetName pointing at the named transform — WinUI 3
    Storyboards have stricter resolution

The drawer XAML itself (Views/SettingsDrawer.xaml + .cs) is unchanged
and ships alongside this commit. Re-host it in MainWindow.xaml once
the parse error is triaged (likely fix: replace TranslateTransform.X
animation with the AppWindow composition API or use the
CompositionTarget approach instead of a Storyboard).

The migration plan's Phase 3 is now substantially CLOSED — the
WindowsAppSDK activation blocker is resolved (1.8 DDLM swap). Next
session opens with Phase 4 (view-model wiring) plus the SettingsDrawer
re-host triage.
2026-05-13 00:41:49 -04:00
07f4a1b716 docs(work-log): add root-cause finding for activation blocker
Some checks failed
CI / build-and-test (push) Failing after 27s
MddBootstrapInitialize2 probe identifies HR=0x80670016 =
MDD_E_BOOTSTRAP_INITIALIZE_DDLM_NOT_FOUND. Three suggested fixes
documented inline, with option 2 (switch to WindowsAppSDK 1.8, which
has its DDLM installed) already taken in commit 166e7d6.

After fix #2 the bootstrap succeeds and the .exe launches, but a
secondary XAML parse error (HR=0x802b000a) terminates it within 1s.
Triaging that is the next session's task.
2026-05-13 00:40:02 -04:00
166e7d6e6a build(winui3): switch to WindowsAppSDK 1.8 + add diagnostic probe
Some checks failed
CI / build-and-test (push) Has been cancelled
Two big findings from a custom MddBootstrapInitialize2 P/Invoke probe
this session:

1. The original WinUI 3 activation failure ("this application could not
   be started") was MDD_E_BOOTSTRAP_INITIALIZE_DDLM_NOT_FOUND (HR
   0x80670016). The framework package Microsoft.WindowsAppRuntime.1.6
   was installed, but the Dynamic Dependency Lifetime Manager sibling
   (MicrosoftCorporationII.WinAppRuntime.Main.1.6) wasn't. This machine
   has Main.1.5 and Main.1.8 packages but no Main.1.6, so bootstrap for
   1.6 fails.

2. Switching the WindowsAppSDK NuGet to 1.8.250916003 + the bootstrap
   major.minor to 0x00010008 in Program.cs gets past activation. The
   .exe now launches and Bootstrap.TryInitialize returns S_OK. The 1.8
   DDLM is present and the runtime spins up.

Also lands `src/TeamsISO.App.WinUI.Probe/`, a tiny console diagnostic
that calls MddBootstrapInitialize2 directly via P/Invoke (bypassing the
full WindowsAppSDK NuGet to avoid the MRT/PRI MSBuild tasks that need
VS's AppxPackage tooling installed). The probe prints the HResult and a
human-readable description; use it to triage WindowsAppSDK activation
on any deployment target:

  dotnet run --project src/TeamsISO.App.WinUI.Probe

A SECOND ISSUE surfaces after activation: the .exe crashes 1 second
after launch with 0xC000027B inside Microsoft.UI.Xaml.dll, sub-code
0x802b000a (XAML_E_PARSER_GENERAL_ERROR). The participants ItemsRepeater
with {Binding ...} markup is suspect (WinUI 3 prefers x:Bind with
x:DataType, and Visibility="{Binding bool}" needs a converter). The
ItemsRepeater is stubbed out to a plain "Participants list renders here"
TextBlock placeholder for now; same crash recurs, so the XAML issue is
elsewhere — likely in Controls.xaml (one of CharacterSpacing /
TextCaption / etc. unsupported), in App.xaml's MergedDictionary chain,
or in MainWindow.xaml's Storyboard target.

Triaging the XAML parse error is the next session's first action. The
sub-code 0x802b000a will help (search WindowsAppSDK source for the
matching XAML parser error). The migration plan in
docs/superpowers/plans/2026-05-12-winui3-migration.md is updated.

Build remains clean.
2026-05-13 00:39:43 -04:00
1687e0c1f5 docs: CHANGELOG + README cover the in-flight WinUI 3 redesign
Some checks failed
CI / build-and-test (push) Failing after 26s
CHANGELOG.md gains an Added section at the top of [Unreleased] that
walks the redesign decisions: PRODUCT/DESIGN docs, WinUI 3 scaffold,
MainWindow IA, ThemeManager, Settings drawer, Help/About/Onboarding,
HTML preview, migration plan. Calls out the WPF host as the still-
shipping build until WinUI 3 reaches feature parity.

README.md picks up:
- A Status section paragraph naming the in-flight redesign and the
  current activation blocker, with a pointer to Phase 3 of the
  migration plan
- A Build section that names both hosts so a fresh checkout doesn't
  surprise contributors with the new csproj
- Documentation section now links PRODUCT.md, DESIGN.md, the migration
  plan, and the interactive HTML preview

Both docs land BEFORE Phase 4 (view-model wiring) so onlookers
understand what's already done and what's queued.
2026-05-13 00:28:37 -04:00
19072b4add docs(work-log): refresh with complete commit list + push confirmation
Some checks failed
CI / build-and-test (push) Failing after 27s
Updates the overnight 2026-05-12 work log to reflect:

- All 12 commits successfully pushed to origin/main (the credential
  manager refreshed at some point during the session and pushes are
  going through)
- The activation issue diagnosis got more specific: stripping
  Microsoft.WindowsDesktop.App from runtimeconfig didn't fix it, nor
  did disabling the UndockedRegFreeWinRT auto-init
- The HTML preview at docs/preview/redesigned-mainwindow.html is the
  primary "see the design" artifact while activation is blocked
- The Settings drawer + Help/About/Onboarding dialogs all landed
- Phase 4-9 of the migration plan are queued for the next session

Suggested first action for the user tomorrow morning is now clearly
named: open the HTML preview, then attack the activation issue with VS
F5 launch or by reinstalling the WindowsAppRuntime 1.6 redist.
2026-05-13 00:27:05 -04:00
6b45c398e0 fix(preview): drawer uses display:none + animation when opened
Some checks failed
CI / build-and-test (push) Failing after 27s
Replace the transform-only approach with display:none / display:flex
switching, plus a @keyframes drawer-slide-in for the entry animation.
The previous translateX trick let the drawer-head close button's SVG
bleed through somehow (likely a browser rendering quirk on a 4K display
with HiDPI scaling); display:none guarantees the hidden state stays
fully hidden across all browsers.

Visual is the same when the drawer is open; only the closed state is
hardened. Drawer still slides in on the rail settings button or the
banner "Open settings" CTA.
2026-05-13 00:26:02 -04:00
46b1ca5874 fix(preview): clip drawer behind .content with position:relative+overflow:hidden
Some checks failed
CI / build-and-test (push) Failing after 28s
The drawer was bleeding its close-X out of the window's left side
because .content (its containing block) had display:grid but no
position:relative, so absolute-positioned children anchored to .window
or further up the tree. Adding position:relative makes the drawer
anchor to .content, and overflow:hidden clips the off-screen
translateX(100%) so its content doesn't render outside the bounds.
2026-05-13 00:22:42 -04:00
2f9f7092ed build(winui3): post-build target to strip WindowsDesktop.App from runtimeconfig
Adds a StripWindowsDesktopAppFromRuntimeConfig MSBuild target that
runs after GenerateBuildRuntimeConfigurationFiles and rewrites
TeamsISO.runtimeconfig.json to drop the implicit
Microsoft.WindowsDesktop.App framework reference. The .NET 8 SDK adds
that framework for any -windows TFM, but WinUI 3 doesn't use it and
including it in the runtimeconfig contributes to the broken
unpackaged activation path (one of the three suspects in the migration
plan's Phase 3).

Build log confirms the strip on every build:
  Stripped Microsoft.WindowsDesktop.App from .../TeamsISO.runtimeconfig.json

Verified the runtimeconfig now reads:
  { "name": "Microsoft.NETCore.App", "version": "8.0.0" }
  (no WindowsDesktop.App entry)

This didn't resolve the activation dialog on its own, but it's a
required step for any unpackaged WinUI 3 build and the next debugging
session can rule it out as a contributing cause.
2026-05-13 00:21:33 -04:00
2909d8b1d7 feat(winui3): wire Settings drawer slide-in animation into MainWindow
Hosts SettingsDrawer in the main content grid as a fixed-width 400px
panel positioned at the right edge, with a TranslateTransform pre-set
to X=400 so it starts off-screen. The rail's settings icon triggers a
220ms ease-out-quart slide-in storyboard; CloseRequested (or the
drawer's Esc/close button) triggers a 180ms ease-in slide-out.
IsHitTestVisible toggles in sync so the off-screen drawer doesn't
intercept clicks on the participants list.

This is the structural commitment from DESIGN.md: settings live in a
right-drawer (not a permanent 380px panel), the participants table
reclaims full width when settings aren't being edited.

Builds clean.

Followup: tab content currently rebuilds imperatively in code-behind;
wire it to a SettingsViewModel mirror of the WPF host's
GlobalSettingsViewModel once the view-model migration starts (Phase 4
of the migration plan).
2026-05-13 00:20:23 -04:00
c150bce28e docs: interactive HTML preview of the redesigned MainWindow
docs/preview/redesigned-mainwindow.html — a single-file faithful
rendering of the redesigned MainWindow at 1280×780 fidelity. Built so
you can SEE the redesign tomorrow morning even with the WinUI 3
activation issue unresolved.

What's interactive:

* Title-bar sun/moon icon toggles dark <-> light. The full CSS variable
  set swaps in-place; both themes are accurate to DESIGN.md's token
  table (Wild Dragon cyan stays as the surface fill in both; accent.cyan
  for text darkens to #0E7C82 on the light palette for AA contrast).
* Rail "settings" icon (the gear) slides the settings drawer in from
  the right with a 220ms ease-out-quart transition.
* Esc dismisses the drawer.
* Banner "Toggle dark / light" and "Open settings" buttons for
  hover-discoverable parity.

What's faithful to the WinUI 3 implementation:

* All structural decisions from the shape brief: 64px rail, 44px
  title bar with absorbed live pills (live · 00:14:32 / rec 3 / 482GB
  free), section header with primary button + secondary actions, 64px
  table rows with avatar+name, signal+lock dot, audio meter,
  output-name in mono, ISO pill at right, conditional in-call control
  bar, slim 32px status bar.
* Active-speaker row (Maya) has the cyan left border + cyan-muted
  background tint, matching the data trigger in MainWindow.xaml.
* Wild Dragon brand mark in the rail uses the same cyan-muted square +
  cyan-text "W" treatment as the WinUI 3 Avatar style.
* In-call buttons use the destructive style for Muted + Leave (coral
  border and text) and Secondary for Camera / Share / Marker, mirroring
  the button-hierarchy commitment from DESIGN.md.
* Settings drawer shows the Appearance tab with the System / Dark /
  Light radio group + the accent peek panel.

Open it in any modern browser:
file:///C:/Users/zacga/Documents/Claude/Projects/Teams%20ISO/docs/preview/redesigned-mainwindow.html

This is documentation; the actual product is the WinUI 3 XAML in
src/TeamsISO.App.WinUI/. Once activation unblocks, the preview can be
deleted (or kept for future stakeholder demos before binaries ship).
2026-05-13 00:18:55 -04:00
8e29c1dc1e build(winui3): suppress UndockedRegFreeWinRT auto-init; document chase
Adds <WindowsAppSdkUndockedRegFreeWinRTInitialize>false</...> with a
comment chain that traces the runtime activation failure investigation
to the next maintainer:

1. WindowsAppSDK's UndockedRegFreeWinRTCommon.targets only auto-enables
   the ModuleInitializer when WindowsAppSDKSelfContained=true.
2. Without it, framework-dependent unpackaged builds need our own
   explicit Bootstrap.TryInitialize call (Program.cs already does this).
3. WITH it, the bundled auto-init P/Invokes Microsoft.WindowsAppRuntime.dll
   during module load — but the runtime DLL lives in the framework MSIX
   package, not the output dir, and Bootstrap hasn't yet added the
   framework dir to the DLL search path. The P/Invoke fails and the
   .exe dies before Main runs.

Setting the property to false explicitly suppresses the early P/Invoke
so our Program.Main + Bootstrap.TryInitialize can sequence correctly.

This didn't fix activation on this build host though — the .exe still
shows "this application could not be started." Strong suspicion: the
managed assembly references Microsoft.WinUI.dll which itself has
DllImport-style dependencies the .NET host probes during assembly load.

Recommended next steps (not done overnight to avoid further blind
swings): attach a debugger to TeamsISO.exe before Main runs (windbg
sxe ld for the runtime DLL, or VS 'Just My Code: off' attach), capture
the CLR fusion log, or try a known-good Microsoft WinUI 3 template
side-by-side to isolate whether the issue is project or machine.

Build remains clean. WPF host unaffected.
2026-05-13 00:16:11 -04:00
48ca16bc5e 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
2e6d2a1e5e docs: WinUI 3 migration plan + overnight 2026-05-12 work log
Two new docs to land alongside the in-flight WinUI 3 work:

* docs/superpowers/plans/2026-05-12-winui3-migration.md
  Full nine-phase migration plan. Locks the architectural decisions
  (WindowsAppSDK 1.6 LTS, unpackaged, win-x64 RID, custom Main with
  explicit Bootstrap, CommunityToolkit DataGrid 7.1.2, AppWindow
  title-bar API). Tracks what's done (Phase 1 + 2: scaffold and
  MainWindow shell), what's blocked (Phase 3: activation failure),
  and what's next (Phase 4-9). Risk register flags fallback paths.

* docs/superpowers/work-log-2026-05-12.md
  Operator-readable summary of overnight progress. Leads with the
  pull-and-push reminder (forgejo credentials expired so commits are
  local-only until Zac authenticates and pushes manually), names the
  activation blocker with the diagnostic evidence captured, and
  suggests the first session tomorrow morning. Documents what was
  deliberately NOT touched (WPF host, Teams orchestration, view-model
  wiring) so the running build is unambiguously safe.
2026-05-13 00:09:51 -04:00
db341f9446 build(winui3): pin RID + flatten native DLLs into output dir
Locks RuntimeIdentifier=win-x64 so MSBuild flattens the WindowsAppSDK
native runtime files (Microsoft.WindowsAppRuntime.Bootstrap.dll +
WebView2Loader.dll, both in runtimes/win-x64/native/) directly alongside
TeamsISO.exe at build time, instead of leaving them in a runtimes/
subfolder where the loader can't find them at activation.

Also defers app.manifest from build pending the bootstrapper hardening
follow-up. WinUI 3 emits its own manifest with the DPI awareness +
supportedOS GUIDs that match what we want; reintroducing ours alongside
that needs a uap:VisualElements merge.

Build is clean. Activation still blocked on a separate WinUI 3
unpackaged-launch issue that doesn't reach Main(); diagnostics
captured in COREHOST_TRACE=1 confirm .NET host loads correctly through
CoreCLR.dll, the failure is downstream in the WinUI / WindowsAppSDK
activation path.

The WPF host remains the running build until the activation issue is
resolved.
2026-05-13 00:07:05 -04:00
9e176d8f10 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
cb1402ec8d 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
94b0a71edc docs: PRODUCT.md + DESIGN.md (ground-up GUI redesign brief)
Captures the impeccable context for the GUI redesign greenlit on 2026-05-12:

PRODUCT.md - register=product, primary persona=solo broadcast operator at
1:50am with a live show twenty minutes out. Strategic principles foreground
progressive disclosure over progressive density. Anti-references explicitly
name the "vibe-coded GUI" failure mode (cards-in-a-grid, hero-metric template,
Zoom pastel, generic SaaS dashboard) so the redesign can be measured against
what it must not become.

DESIGN.md - tokens for the WinUI 3 replatform target. Dark + light palettes
as ThemeDictionary entries, context-aware accent split (accent.cyan.surface
for fill, accent.cyan.text for the darker AA-passing variant on light bg).
Theming via {ThemeResource}; toggle in title bar + settings drawer; System/
Dark/Light tri-state persisted in UIPreferences.Theme.
2026-05-12 23:45:04 -04:00
f12cbe7517 docs: _NEXT.md captures the full E.4 + autorec + UX-pass batch
Some checks failed
CI / build-and-test (push) Failing after 49s
2026-05-10 21:29:27 -04:00
70137147d6 docs: CHANGELOG covers MUTED pills / free space / loudest sort / snapshot-all / numpad / active-speaker highlight
Some checks failed
CI / build-and-test (push) Failing after 31s
2026-05-10 21:28:36 -04:00
b0029a51bf Highlight active speaker row with cyan left border
Some checks failed
CI / build-and-test (push) Has been cancelled
Visual cue for who's currently speaking — operators don't need to watch every VU bar. MainViewModel.OnStatsTick scans enabled participants once per tick, picks the loudest above a 0.05 floor (anti-flicker threshold), sets IsActiveSpeaker on the winner and clears on everyone else. DataGridRow DataTrigger swaps in a 3px cyan-accent left border + CyanMuted background tint when IsActiveSpeaker is true.

Plays well with sort modes: LoudestFirst makes the highlighted row always the topmost; other sort modes leave the row position alone, just paints the indicator.
2026-05-10 21:28:09 -04:00
9db0875f9e Numpad 1-9 hotkeys toggle Nth participant's ISO
Some checks failed
CI / build-and-test (push) Failing after 31s
Fast keyboard-driven ISO routing for operators with one hand on the keyboard during a show. Both NumPad1..9 and top-row 1..9 bind to ToggleByIndexCommand which resolves against the filtered+sorted ParticipantsView — index matches what's on screen, not the underlying storage order.

Press a digit again to toggle off. Plays nice with sort modes: LoudestFirst means '1' is always whoever's loudest right now; Alphabetical lets you build muscle memory for recurring guests.

Implementation:

- New generic RelayCommand<T> in RelayCommand.cs so XAML CommandParameter strings convert to the action's T (int / string / etc.).

- ToggleByIndexCommand on MainViewModel iterates ParticipantsView, finds the Nth ParticipantViewModel, fires its ToggleIsoCommand if CanExecute.

- 18 KeyBindings (9 NumPad + 9 D1-D9) in MainWindow.xaml's Window.InputBindings.

- F1 help cheat sheet updated to mention the new range.
2026-05-10 21:26:37 -04:00
10a0826fb3 Snapshot all enabled participants in one click
Some checks failed
CI / build-and-test (push) Failing after 27s
Header button 'Snapshot all' fires SnapshotAllCommand which iterates every enabled participant, grabs the latest ProcessedFrame, encodes as PNG into a fresh timestamped subfolder under %USERPROFILE%\\Pictures\\TeamsISO\\snapshots-yyyyMMdd_HHmmss\\. One folder per click so back-to-back snapshot sessions don't comingle.

Reuses the per-participant snapshot path established earlier — same WriteableBitmap(Bgra32) → PngBitmapEncoder pipeline. Reports saved + failed counts in the toast so the operator knows if anything was missed (typical failure: pipeline still warming up, no frame yet).
2026-05-10 21:23:49 -04:00
d6793d8d9c Sort participants by Loudest (active speaker at top)
Some checks failed
CI / build-and-test (push) Failing after 43s
Adds a fourth participant sort mode: LoudestFirst, sorts by DisplayedAudioLevel descending so the current active speaker bubbles to the top of the DataGrid. Operators reacting to who's talking can see the active speaker without scanning the list.

Refresh-on-tick (1Hz) only fires when LoudestFirst is active — other sort modes don't change keys every tick so they skip the cost. ParticipantViewModel.DisplayedAudioLevel already has a decay envelope (max-of-new-or-decayed-old at 0.7 per tick), which prevents jittery reorder on every audio frame.

Persisted via the existing UIPreferences.ParticipantSort enum (new value tacked onto the end so older ui-prefs.json files default to JoinOrder cleanly).
2026-05-10 21:21:45 -04:00
5c491c9d83 Footer shows recording drive free space
Some checks failed
CI / build-and-test (push) Failing after 32s
Operators recording long shows previously had to open File Explorer to check disk pressure. New '· 245 GB free' indicator next to the REC badge polls DriveInfo on the recording drive at the existing 1Hz stats tick. Coral tint kicks in below 10GB; existing DiskSpaceWatcher still auto-disables recording at 1GB as a hard safety net.

FormatBytes helper produces footer-readable strings: '1.2 TB' / '245 GB' (no decimal for 100+ GB to avoid clutter) / '8.4 GB' (decimal for the low-warning case) / '450 MB'.

Polling is wrapped in try/catch — network paths occasionally throw, and disk-space display is a comfort feature, not a critical signal.
2026-05-10 21:19:34 -04:00
61dce2eecd IN-CALL bar shows MUTED / CAM OFF pills
Some checks failed
CI / build-and-test (push) Failing after 34s
Operators with auto-hide Teams couldn't tell if they were muted or had their camera off — needed to restore Teams just to check. New coral pills in the IN-CALL bar surface the local-user state, populated from a single UIA traversal that also drives the IN-CALL pill (so the cost stays at one walk per stats tick, not three).

Detection: TeamsControlBridge.DetectCallState returns a CallStateSnapshot with IsInCall + IsMuted + IsCameraOff. The Mute and Camera buttons toggle their UIA Name between 'Mute'/'Unmute' and 'Turn camera off'/'Turn camera on' depending on state; check the more-specific candidate (unmute / turn camera on) first to avoid false positives from substring matching.

Localized for EN / DE / ES / FR / PT / JA — same locale list the candidate-name arrays already cover. Pills visible only when both in-call AND the corresponding state is true; once you unmute, the pill vanishes within ~1s (next stats tick).
2026-05-10 21:17:19 -04:00
33f145624e docs: CHANGELOG reflects auto-record + E.4 embed + snapshot + recording-duration
Some checks failed
CI / build-and-test (push) Failing after 29s
2026-05-10 21:15:23 -04:00
cc29c503a9 Phase E.4 experimental: SetParent-embed Teams window inside TeamsISO
Some checks failed
CI / build-and-test (push) Failing after 28s
Reparents Teams' main top-level window into a TeamsISO-owned host via Win32 SetParent + window-style stripping. Operator gets Teams visually INSIDE TeamsISO instead of as a separate window — completes the 'Teams runs within this app' direction the user asked for after auto-hide.

Strictly opt-in (DISPLAY tab → 'Embed Teams window (experimental)'). Modern Teams runs WebView2 in its main window; WebView2 is sensitive to parent changes and may render glitches or refuse focus. If so, operator unticks and falls back to auto-hide mode.

Implementation:

- TeamsLauncher.EmbedTeamsInto(hostHwnd, w, h): finds Teams' main window (longest-title heuristic — same as GetActiveWindowTitle), saves original parent + WS_STYLE, SetParents into host, strips WS_CAPTION + WS_THICKFRAME + WS_BORDER + WS_DLGFRAME + WS_POPUP, adds WS_CHILD, MoveWindow to fit.

- TeamsLauncher.RestoreEmbed(): SetParent back to desktop + restore saved window styles. Idempotent — safe to call on shutdown even if nothing was embedded.

- TeamsLauncher.ResizeEmbedded(w, h): MoveWindow to new dimensions; called from host SizeChanged event.

- New TeamsEmbedWindow chromeless host with an EXPERIMENTAL pill in the caption. Loaded → grab HwndSource from EmbedHost Border → call EmbedTeamsInto. SizeChanged → ResizeEmbedded. Closed → RestoreEmbed (in try/finally so a crash can't leave Teams orphaned). Friendly fallback messages if no Teams window exists or HWND grab fails.

- Settings → DISPLAY → checkbox + 'Open embed window' button (gated by the checkbox). Persisted via EmbedTeamsWindow on UIPreferences.
2026-05-10 21:14:42 -04:00
aa07ad9f08 Auto-record when Teams joins a meeting
Some checks failed
CI / build-and-test (push) Failing after 26s
New AutoRecordOnCall preference (DISPLAY tab). When checked, recording auto-flips ON the moment Teams transitions into a call (UIA Leave button appears in tree), and auto-flips OFF when the call ends.

Completes the unattended-show story: with Launch + AutoHide + AutoRecord all ticked, the operator launches TeamsISO and walks away — Teams runs invisibly, recording begins/ends with the meeting, ISOs route, all done. Toast surfaces each transition so they know what's happening if they glance at the screen.

Implementation: transition detection lives in the existing UIA-probe code in OnStatsTick. previousInCall != inCall gate prevents the auto-toggle from re-firing on every poll. Direct call to _controller.SetRecording + Settings.RecordIsosToDisk = ... so the existing recording infrastructure handles the rest. Toast for visibility, swallow-on-error so a recording config issue can't break the IN-CALL pill update path.
2026-05-10 21:10:30 -04:00
1d5d055b68 Right-click → Save current frame: snapshot ProcessedFrame as PNG
Some checks failed
CI / build-and-test (push) Failing after 29s
New context-menu action grabs the latest ProcessedFrame from IIsoController.GetLatestProcessedFrame and encodes it as a PNG under %USERPROFILE%\\Pictures\\TeamsISO\\. Filename includes participant display name + timestamp so back-to-back snapshots don't collide.

Encoding path: WriteableBitmap(Bgra32) wraps the frame's pixel buffer verbatim (engine output is already top-down BGRA32), PngBitmapEncoder writes it. No re-encoding losses. Toast tells the operator where the file landed.

Best-effort: if no frame is available yet (just-spun-up pipeline), warns rather than throws. Useful for highlight reels, social posts, attaching to bug reports.

ParticipantViewModel gained an optional ToastViewModel constructor parameter so snapshot feedback surfaces in the existing toast. Wiring updated at the one call site in MainViewModel.
2026-05-10 21:08:40 -04:00