Commit graph

32 commits

Author SHA1 Message Date
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
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
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
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
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
acc569dd24 Onboarding step + Open /ui button + recording duration in footer
Some checks failed
CI / build-and-test (push) Failing after 30s
Three small UX wins:

1. Onboarding gained step 5 ('Run Teams headless') and step 6 ('Drive from another machine') so new operators discover the auto-launch/auto-hide + LAN-reachable workflows. Existing 'where things live' step renumbered to 7.

2. Settings → DISPLAY → Control surface URL row gains an Open button next to Copy that fires the URL into the default browser via Process.Start with UseShellExecute. Operators previewing how the embedded /ui control panel looks on a phone/tablet no longer need to copy-paste manually.

3. Recording badge in footer now shows 'REC 3 · 12:45' instead of just 'REC 3'. RecordingElapsed VM property maintains a separate timer from the session timer because recording can start AFTER the meeting begins; operators tracking 'how long has the archive copy been rolling' need that distinct duration.
2026-05-10 21:05:30 -04:00
b9147183ce Quick-join Teams meetings from URL — paste link, click Join
Some checks failed
CI / build-and-test (push) Failing after 27s
Adds a small URL input + Join button to the IN-CALL bar. Operators paste a https://teams.microsoft.com/l/meetup-join/... or msteams:/l/meetup-join/... link, click Join, and Teams launches into the meeting in one shot. Eliminates the open-Teams → Calendar → find meeting → click join dance — operators get meeting links from email/Outlook and can now join straight from TeamsISO.

TeamsLauncher.TryJoinMeeting validates the URL targets Teams (only http(s) URLs containing teams.microsoft.com / teams.live.com, or msteams: deep-links — won't shell-exec arbitrary clipboard contents). On success, integrates with AutoHideTeamsWindows so the Teams meeting window briefly appears then vanishes; operator is in the call, driving routing from TeamsISO.

VM-side: MainViewModel.JoinMeetingCommand + JoinMeetingUrl two-way bound. Field clears on success; warn-toast on failure with the specific reason (empty / not-a-teams-url / launch-failed).
2026-05-10 20:45:04 -04:00
a9a10e01a4 IN-CALL bar surfaces Teams meeting state — 'READY' / 'IN CALL'
Some checks failed
CI / build-and-test (push) Failing after 27s
Operators using auto-hide Teams couldn't tell whether they were in a meeting without restoring the Teams window. New status pill in the IN-CALL bar header shows:

  • empty when Teams isn't running

  • 'READY' (gray dot) when Teams is running but not in a call

  • 'IN CALL' (cyan dot) when Teams is in an active meeting

Detection: TeamsControlBridge.IsInCall() walks Teams' UIA tree looking for the Leave / Hang-up button. Present iff in a call — works across Teams versions because Teams only exposes the Leave control while a call is active. Same candidate-name list the LeaveCall command uses, with localized strings for EN/DE/ES/FR/PT/JA already in place.

Polled at the existing 1Hz stats tick. UIA traversal can take 50-200ms in a busy call, so the probe runs off-thread; the property update is dispatched back via _dispatcher.InvokeAsync. Failure paths swallow exceptions — a flaky UIA call must never crash the stats timer.

159/159 tests passing, 0 warnings, 0 errors.
2026-05-10 20:42:57 -04:00
d8186c5eb8 Auto-launch + auto-hide Teams: 'I only see TeamsISO' experience
Some checks failed
CI / build-and-test (push) Failing after 28s
Two new persisted preferences in DISPLAY settings, paired to give operators the 'launch TeamsISO, never see Teams' experience the user asked for:

- LaunchTeamsOnStartup: TeamsISO auto-starts Teams in the background each launch (fire-and-forget background task in App.OnStartup, after the main window has materialized so a slow Teams launch doesn't delay the UI).

- AutoHideTeamsWindows: as soon as Teams' windows materialize after launch, hide them. New TeamsLauncher.AutoHideAfterLaunchAsync runs a polling loop (250ms / up to 15s) that catches the splash, main window, and any follow-up panels Teams opens. Teams takes 2-5s to render its main window and the splash arrives separately, so a one-shot hide right after launch wouldn't be enough.

When TeamsISO starts and Teams is already running (from a prior session), the auto-hide path still fires so the 'I only see TeamsISO' rule applies even when Teams was launched externally.

Operator drives everything through the IN-CALL bar (mute / camera / share / leave / marker) + participants DataGrid (ISO routing). Eye-toggle in the rail still restores Teams windows on demand.

Both toggles default to off — opt-in. Persisted via UIPreferences so they survive process restart.
2026-05-10 20:35:00 -04:00
598938ede5 Fix sidebar text cutoff + Teams launch ambush dialog
Two user-reported bugs:

1) CheckBox content was clipping in the 380px settings panel ('Control surface (Stream Deck / Companion / w...' / 'LAN-reachable (allow other machines on yo...'). The Wd.CheckBox template used a horizontal StackPanel which doesn't bound child width, so long Content strings ran off the column without wrapping. Replaced StackPanel with a Grid (Auto + *) and injected a TextBlock style with TextWrapping=Wrap into the ContentPresenter resources — when WPF auto-wraps a string Content in a TextBlock, the resource lookup gives it Wrap.

2) The rail Launch Teams button ambushed operators: clicking with Teams already running (which is common when the eye-toggle has hidden Teams' windows) opened a 'Close all Teams windows now?' dialog. Operators expect Launch to mean 'show me Teams', not 'stop Teams'. Split the actions:

   - Left-click: Teams not running → launch; Teams hidden → restore + foreground; Teams visible → bring to front. Always idempotent-progressive.

   - Right-click: ask to stop Teams (preserves the kill path for those who want it).

TeamsLauncher.TryLaunch now collects per-attempt errors instead of swallowing them — a real failure surfaces 'ms-teams: URI → <reason>' / 'AppsFolder shell → <reason>' / 'classic Update.exe → not found at <path>' so 'No Teams found' isn't a black box.

Also added a 2nd path: explorer.exe shell:appsFolder\\\\MSTeams_8wekyb3d8bbwe!MSTeams (AppX activation via the OS's own Start-menu verb) as a fallback if the URI handler is misconfigured. Removed the broken bare-stub call to %LOCALAPPDATA%\\\\Microsoft\\\\WindowsApps\\\\ms-teams.exe — that's a 0-byte AppX placeholder that never worked outside an AppX context.
2026-05-10 14:39:04 -04:00
8e66491e09 Add manual X close to toast notification
Some checks failed
CI / build-and-test (push) Failing after 26s
Toast was auto-dismiss-only (3s timer). Operators running a live show want to clear visual clutter without waiting — added a small X button to the right of the message that calls ToastViewModel.DismissCommand (stops timer + hides immediately).

Implementation: ToastViewModel gained a DismissCommand RelayCommand and a Hide() helper. MainWindow toast overlay gained a 20x20 button bound to the command, custom inline template (rounded transparent bg, hover lifts to Wd.Button.HoverBg).
2026-05-10 14:05:28 -04:00
6d9407a61f Add LAN-reachable mode to control surface and OSC bridge
Some checks failed
CI / build-and-test (push) Failing after 27s
When the new ControlSurfaceLanReachable preference is on, both the REST/WebSocket control surface and the OSC bridge bind to all interfaces (http://+:port/ via HttpListener wildcard, IPAddress.Any for OSC) instead of loopback. The settings VM persists the toggle, restarts both surfaces when flipped, and surfaces a ControlSurfaceUrl computed from the first non-loopback IPv4 + a Copy button so operators can paste the URL onto a control PC.

Use case: a headless host PC runs Teams + TeamsISO; a thin client on the same LAN drives it via /ui or a Stream Deck. Closed-network deployment, no auth — documented as a trusted-LAN-only mode in docs/CONTROL-SURFACE.md, including the one-time 'netsh http add urlacl url=http://+:9755/ user=Everyone' requirement and the firewall rule.
2026-05-10 10:01:32 -04:00
57c2922d1c feat(ui): auto-disable ISOs when participants leave the meeting 2026-05-10 09:41:28 -04:00
b2666236ec feat(ui): toast feedback for settings actions; refresh _NEXT.md
Adds a small auto-dismissing pill notification at the bottom-center of the participants area: 'Settings saved' on Apply Changes, 'Transcoder topology applied — restart Teams to take effect' after the one-click NDI groups setup. ToastViewModel owns its own DispatcherTimer and resets the dismissal countdown on successive calls, so the most recent message is always the one visible. Hooked into MainViewModel and threaded into GlobalSettingsViewModel via constructor injection.

_NEXT.md rewritten to reflect the May 2026 hardening pass: separates engine / UI / networking / Phase E.1 / diagnostics / CI / tests sections, lists every shipped item, and re-prioritizes the remaining work (Phase E.2-E.3 embedded Teams, code-signing the MSI, refresh-discovery affordance, output thumbnail previews, settings panel UX, auto-disable on departure, operator presets).
2026-05-09 09:30:04 -04:00
ab072979d8 feat(ui): empty-state, pipeline error/no-signal indicators, JetBrains Mono, tooltips
Some checks failed
CI / build-and-test (push) Failing after 30s
Four polish improvements aimed at production-floor usability.

1. Empty-state placeholder for the participants card. When Participants.Count == 0, the DataGrid is hidden in favor of a friendly 'Waiting for Teams' panel: faded dragon mark, headline, explainer, and a four-item checklist (Teams running? NDI broadcast on? Discovery group correct? Firewall clear?). New CountToVisibilityConverter (with optional 'empty' parameter to invert) drives both the placeholder and the DataGrid visibility from the same Participants.Count source.

2. Per-pipeline error / no-signal surfacing. IsoHealthStats grows an init-only State property populated from IsoPipeline.State. ParticipantViewModel.UpdateStats maps that to a StateLabel ('LIVE' / 'NO SIGNAL' / 'ERROR' / 'STARTING' / '—'). The ISO toggle button gains DataTriggers on StateLabel — coral-tinted '● ERROR' when the supervisor gives up, amber-tinted '● NO SIGNAL' when the slate threshold trips. Operators can see at a glance which pipelines are broken.

3. JetBrains Mono Variable v2.304 (OFL) bundled at Assets/Fonts/JetBrainsMono.ttf. Wd.Font.Mono now points at the embedded font so machine names, timecodes, and stat counters render in JetBrains Mono regardless of system fonts. Falls back to Cascadia Mono / Consolas if the resource is missing.

4. Tooltip pass over every interactive control in the settings panel (framerate / resolution / aspect / audio / discovery group / output group / hide-local checkbox / Apply button / per-row Output Name textbox / per-row ISO toggle). Operators learn affordances on hover instead of by trial and error.

Tests: 76/76 unit + 9/9 NDI integration green.
2026-05-08 19:32:19 -04:00
0c82ac71f0 feat: bundle Inter font, emergency stop button, window persistence + tests
Some checks failed
CI / build-and-test (push) Failing after 27s
Four polish items + a test pass.

1. Inter Variable (rsms/inter v3.19, OFL) is bundled at Assets/Fonts/Inter.ttf (~800 KB) and registered as a WPF Resource. WildDragonTheme.xaml's Wd.Font.Sans now points at pack://application:,,,/Assets/Fonts/#Inter so the typography matches wilddragon.net regardless of whether the user has Inter installed system-wide. Falls back to Segoe UI Variable Display if the resource is missing.

2. 'Stop all ISOs' button at the right of the participants header. Bound to a new MainViewModel.StopAllIsosCommand that snapshots the enabled list, awaits DisableIsoAsync sequentially, and silently swallows per-pipeline failures (best-effort emergency stop). CanExecute gates on whether any ISO is currently enabled.

3. WindowStateStore service persists the main window's Left/Top/Width/Height/State to %LOCALAPPDATA%\\TeamsISO\\window.json on close and restores it on SourceInitialized. Multi-monitor friendly: a saved position with no corner inside any virtual screen is rejected so a disconnected monitor doesn't strand the window off-screen.

4. Two new unit tests cover FrameProcessor's drops + duplicates accounting. 76/76 unit tests pass (was 74).
2026-05-08 13:59:14 -04:00
ff7e949466 fix(ui): hit-test the dragon button so the About dialog actually opens
Some checks failed
CI / build-and-test (push) Failing after 26s
WindowChrome.CaptionHeight=44 makes the top 44px of the window a drag region; the dragon-mark rail button lives within it. Without shell:WindowChrome.IsHitTestVisibleInChrome=True, clicks were being eaten by the drag handler instead of firing the button.
2026-05-08 13:52:59 -04:00
e8f52a3153 feat: app icon, FPS, drops counter, --version, About dialog, Stop Teams toggle
Six related polish items, all building on tonight's groundwork.

1. App icon: teamsiso.ico generated from dragon-mark.png at 7 sizes (16-256), wired as ApplicationIcon in the WPF csproj, MainWindow.Icon, AboutWindow.Icon, and ARPPRODUCTICON in the WiX MSI. Taskbar / window / Add-Remove-Programs all show the dragon mark now.

2. Running incoming FPS: ring buffer of last 30 frame timestamps in IsoPipeline; ComputeFps() returns moving-average rate. Surfaced on IsoHealthStats.IncomingFps and shown in the Source column of the participants DataGrid as 'WxH · 59.94 fps'. Resets cleanly on every supervisor restart.

3. Drops counter: FrameProcessor.Stats already aggregated FramesDropped (closest-frame strategy when the receiver outpaces the processor) and FramesDuplicated; just plumbed _liveProcessor through IsoPipeline so GetStats() can read them. Exposed in the Live column under the in/out counters as a coral-tinted 'drop N'.

4. Console --version flag: prints engine version (with embedded git SHA), .NET version, OS, NDI runtime banner, expected prefix, exit-code legend, plus a wilddragon.net link. Useful for support tickets.

5. About dialog: chromeless modal with the dragon mark + version / .NET / OS / NDI runtime fields and a link to wilddragon.net. Triggered by clicking the rail logo.

6. Teams launcher Stop toggle: TeamsLauncher gains IsRunning() and StopAll(). The rail's Teams button now toggles — if Teams is up, ask to close all Teams windows via WM_CLOSE; otherwise launch as before. Confirms before stopping so we don't kill the user's call mid-transition.

Tests: 74/74 unit + 9/9 NDI integration green throughout. MSI builds clean and now embeds the dragon icon for ARP.
2026-05-08 13:50:19 -04:00
01ef4250d7 feat(ui): real Wild Dragon mark in rail + automated transcoder topology
All checks were successful
CI / build-and-test (push) Successful in 31s
Two related deliverables addressing the user's morning asks.

1. Branding: Dragon WHITE.png and Wild Dragon Logo WHITE.png from the brand kit are copied into src/TeamsISO.App/Assets/ and registered as <Resource> items in the .csproj. The rail's placeholder 'W' glyph is replaced by the real dragon mark (40x40, HighQuality bitmap scaling) with a 'Wild Dragon' caption underneath.

2. NDI Access Manager automation: NdiAccessManagerConfig service reads/writes %APPDATA%\\NDI\\ndi-config.v1.json, working in JsonNode trees so we don't clobber unrelated keys. ApplyTranscoderTopology() sets groups.send=[teamsiso-input] and groups.recv=[public, teamsiso-input] so all local senders (Teams + anything else) broadcast on the private group while local receivers can still see public sources too. Engine-side, the user's per-pipeline OutputGroups override pushes TeamsISO outputs back onto Public so downstream switchers see clean ISOs.

Atomic write: temp + replace, with timestamped backup of the prior config. ReadCurrentGroups() can be used by future UI to show what's currently configured. RestoreDefaults() reverts.

Settings panel grows an 'Apply transcoder topology' button under the NDI Network section. Click writes the system config, sets the engine's discovery=teamsiso-input / output=public, refreshes the bound text boxes, and pops a dialog with a 'restart Teams' reminder + the backup path.
2026-05-08 07:19:31 -04:00
c08b90b0b2 feat(ui): Launch Teams rail button + spec for embedded-Teams roadmap
All checks were successful
CI / build-and-test (push) Successful in 40s
First step of Phase E.1 from the new spec at docs/superpowers/specs/2026-05-08-embedded-teams-orchestration.md: a third icon in the left rail launches the Microsoft Teams desktop client as a subprocess of TeamsISO so the operator doesn't have to leave the app to start a meeting.

Services/TeamsLauncher tries the ms-teams: URI first, falls back to %LOCALAPPDATA%\\Microsoft\\WindowsApps\\ms-teams.exe (new Teams), then the classic Update.exe handoff. On failure surfaces a friendly MessageBox with the install link.

The spec doc lays out the full three-phase roadmap (launcher -> window orchestration -> in-app meeting controls via Graph API or UIAutomation) and explicitly calls out what's out of scope (replacing Teams' media stack).

_NEXT.md updated to mark Phase D done and queue Phase E + remaining polish items (code-signing, Inter/JetBrains Mono font bundling, real Wild Dragon dragon-mark, drops counter, running-fps display).
2026-05-08 01:05:26 -04:00
bab29b02ab feat(ui): chromeless title bar with custom caption controls
All checks were successful
CI / build-and-test (push) Successful in 37s
MainWindow drops the standard Windows title bar (WindowStyle=None + WindowChrome with CaptionHeight=44, ResizeBorderThickness=6, UseAeroCaptionButtons=False) and draws its own minimize / maximize-restore / close buttons inline in the existing header strip. The custom buttons opt into shell:WindowChrome.IsHitTestVisibleInChrome=True so clicks fire on them rather than starting a window drag.

Result: the entire top of the window is now ours, matching the Microsoft Teams desktop client's flush header look. The 'TeamsISO + by Wild Dragon' branding sits at the same baseline as the engine-status pill and the caption controls, and dragging anywhere not occupied by an interactive widget moves the window.

Caption-button styles in the theme: 46x32 hover-tinted, with the close button turning the Windows 11 #C42B1C red on hover. Maximize-button glyph swaps between the single-rectangle and overlapping-rectangles variants on StateChanged.

Drive-by: ParticipantViewModel.{FramesIn,FramesOut,IncomingResolution} setters dropped from private to public so {Run Text=...} bindings (which default to TwoWay on Run) can attach without WPF throwing 'cannot work on read-only property'.
2026-05-08 00:55:57 -04:00
9c231118de feat(stats): wire IsoHealthStats end-to-end and surface live counters in UI
IsoPipeline now publishes refs to its currently-live NdiReceiver and NdiSender (set by RunInnerPipelineAsync, cleared on exit) so a stats poll from any thread can read FramesCaptured / FramesSent without entangling the pipeline's lifetime with its observer. The receiver's raw-frame channel is wrapped with a TappedChannelWriter so the most recent RawFrame is captured for source-resolution display, again without changing the receiver's contract.

IsoController.GetStats() drops the stub return-Empty and instead reads the live pipeline.GetStats() outside the gate so a slow stats read can't serialize the controller's other operations.

WPF: MainViewModel runs a 1 Hz DispatcherTimer that pulls stats for every participant view-model and pushes them via UpdateStats(). ParticipantViewModel grows three displayable properties — FramesIn, FramesOut, IncomingResolution — bound into the participants DataGrid as a new 'Live' column showing the down/up frame counts and the source resolution underneath the machine name.

Tests: 74/74 unit + 9/9 NDI integration green; the existing round-trip integration test exercises the new wiring at runtime (live receiver/sender refs are set, frames flow, channels close cleanly).
2026-05-08 00:52:44 -04:00
6cac486fbe feat(ui): rebrand to Wild Dragon + Microsoft Teams layout
Some checks failed
CI / build-and-test (push) Failing after 33s
Replaces the Stone theme with Wild Dragon branding (canvas #0A0A0A, accent cyan #97EDF0, secondary #9AE0FD, coral alert #FB819C — sourced from wilddragon.net) and reorganizes MainWindow into a Microsoft Teams-style three-column layout: a 72px left rail (logo + Participants/Settings nav + engine-status indicator), a center content area (header + participants card), and a right settings panel.

Adds InitialsConverter so participant avatars render real initials (Brendon Power -> 'BP', '(Local)' -> 'L') instead of a generic glyph. Drops the obsolete StoneTheme.xaml; the project now ships exactly one theme dictionary.

Typography: Inter (with Segoe UI Variable Display fallback) for the sans stack, JetBrains Mono (Cascadia Mono fallback) for machine names and timecodes — matching the wilddragon.net site.

Verified live against the running Teams meeting: app launches, participant 'Brendon Power' displays with avatar, settings panel surfaces NDI groups + Hide-Local toggle, engine status pill shows green/live.
2026-05-08 00:08:39 -04:00
b542d01835 feat(ui): rebuild MainWindow with Stone-theme design system
Adopts the design language from Dammyjay93/interface-design: warm Stone neutrals, accent orange (#EA580C), borders-only depth (no shadows), 8px spacing grid, all-caps section labels, mono typography for machine names and timecodes.

Themes/StoneTheme.xaml is the single source of truth for design tokens (color brushes, typography styles, spacing) plus restyled control templates (Button, TextBox, ComboBox, ComboBoxItem, CheckBox, DataGrid + DataGridColumnHeader / DataGridRow / DataGridCell, ScrollBar). MainWindow consumes the tokens and is laid out with a header (title + status pill), section-headed Settings sidebar (Output Format / NDI Network / Display), card-wrapped participant list, and a mono status footer.

Settings sidebar surfaces the new NDI group configuration (discovery + output) and a Hide-(Local) checkbox. The latter filters the user's own self-preview from the participants list at the MainViewModel layer (HideLocalSelf=true by default) so operators don't accidentally route their own preview as an ISO. Apply Changes round-trips both FrameProcessingSettings and NdiGroupSettings through the controller in one go.
2026-05-07 23:58:02 -04:00
d64b110550 feat(ui): add MainWindow XAML with participants DataGrid, settings sidebar, alert banner
Some checks failed
CI / build-and-test (push) Failing after 34s
2026-05-07 15:40:49 +00:00
f21e818b28 chore: scaffold WPF app and integration test projects
- TeamsISO.App: hand-rolled net8.0-windows WPF csproj since the WPF
  template isn't shipped on linux-arm64 .NET SDK; UI is a placeholder
  for Phase C.
- TeamsISO.Engine.IntegrationTests: cross-platform xunit project with a
  skipped scaffold fact tagged [Trait("requires", "ndi")] for Phase B.
- TeamsISO.Linux.slnf: solution filter for non-Windows CI that excludes
  the WPF project (which can only build on Windows).
2026-05-07 15:09:56 +00:00