Commit graph

17 commits

Author SHA1 Message Date
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
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
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
d8adb44a8f Wd.Button.Primary: add disabled / pressed / focus states
Some checks failed
CI / build-and-test (push) Has been cancelled
Primary button (Apply Changes, Save Preset, Confirm-Stop-All) had only an IsMouseOver trigger. Disabled state looked identical to enabled — confusing for the most-frequently-disabled button in the app.

- Disabled: drops to Wd.Accent.CyanMuted at 70% opacity + Text.Disabled foreground.

- Pressed: 85% opacity for the brief tap.

- IsKeyboardFocused: matches the hover treatment so tab-cycling lights it.
2026-05-10 13:36:27 -04:00
2ae0dc2d62 Style ToolTip for dark theme — replace cream Win98 popup
Some checks failed
CI / build-and-test (push) Failing after 38s
Default WPF ToolTip is cream-on-black with a thin 3D border — looks like a Win98 popup on the dark canvas. The app has dozens of tooltips on settings controls, header pills, IN-CALL bar, and per-row toggles — every one was previously rendering as the OS default.

New style: SurfaceElevated background, BorderStrong border, rounded 6px corner, monospace-friendly text wrapping at 320px so a verbose explanation doesn't stretch across the whole monitor.

Implementation note: ContentPresenter doesn't accept TextBlock.TextWrapping as an attached property — used a templated TextBlock bound directly to Content instead (every tooltip in the app passes a plain string).
2026-05-10 13:33:35 -04:00
b56e2e12e1 Style ContextMenu / MenuItem to match dark theme
Some checks failed
CI / build-and-test (push) Failing after 29s
Right-click on a participant row in the DataGrid surfaces actions (Toggle ISO / Restart / Open preview / Record / Copy NDI source name) but rendered with WPF's default white-on-grey ContextMenu — looked like a Notepad popup on the dark canvas.

New ContextMenu + MenuItem styles match the rest of the theme: SurfaceElevated background, rounded corners, slim cyan-tinted hover (matches the button hover treatment from the previous pass), monospaced gesture text in Wd.Text.Tertiary, no chevrons on items without submenus.
2026-05-10 13:32:04 -04:00
0e2927e42c Add keyboard focus rings to themed buttons
Some checks failed
CI / build-and-test (push) Failing after 31s
All Wd.Button.* styles set FocusVisualStyle=x:Null which suppresses WPF's default dotted focus rectangle. With no replacement IsKeyboardFocused trigger, tab-navigating gave NO visual cue — accessibility regression.

Match the keyboard-focus visual to the hover visual so mouse and keyboard land on the same affordance:

- Ghost: cyan border on focus (same as hover, minus the bg fill change so focus + hover compound visibly)

- Caption: lifts to Wd.Button.HoverBg

- RailIcon: lifts to Wd.Button.HoverBg + cyan icon

- IsoToggle: 2px cyan border (same as hover; status bg preserved)

Verified building cleanly. Tab-cycle through the IN-CALL bar / header pills / settings tabs now lights the focused control.
2026-05-10 13:30:56 -04:00
2de65f6d10 Restyle ScrollBar to match dark theme
Some checks failed
CI / build-and-test (push) Failing after 29s
Default WPF ScrollBar template renders chunky Win9x line-up/line-down arrow buttons + a 3D thumb that look out of place on the dark canvas. Replace with a slim transparent track + tinted thumb pattern (Edge / VS Code / GitHub style):

- Thumb: BorderStrong by default, lifts to Text.Tertiary on hover, switches to cyan accent while dragging.

- No arrow buttons — track-clicks above/below the thumb still page-scroll via invisible RepeatButtons (preserves PageUp/PageDown behavior).

- Horizontal orientation has its own template via Style.Triggers so it picks the right Track command set (PageLeft/PageRight).

Affects every ScrollViewer in the app — settings panel, presets dialog list, Notes window, About dialog, Help cheat sheet.
2026-05-10 13:29:44 -04:00
554ab9e570 Make button hover affordances visible on dark canvas
Some checks failed
CI / build-and-test (push) Failing after 27s
Hover state on Wd.Button.Ghost / Caption / RailIcon / IsoToggle was barely visible — the Wd.SurfaceHover fill (#242424) was only 14 luminance points off the canvas (#0A0A0A) and 16 off the cards (#141414), so the hover state read as 'no change' to the user.

Changes:

1. Bumped Wd.SurfaceHover #242424 -> #2A2A2A and Wd.SurfaceActive #2D2D2D -> #363636 across the theme (carries to DataGrid rows etc. with the same too-subtle problem).

2. Added Wd.Button.HoverBg (#33333A) + Wd.Button.PressBg (#3F3F47) — dedicated stronger hover/press fills for buttons specifically. The slight blue tint adds chroma so hover reads obviously distinct from a flat darken.

3. Wd.Button.Ghost hover now also sets BorderBrush=Wd.Accent.Cyan — dual cue (lighter fill + bright cyan border) makes the affordance unmistakable on any surface.

4. Wd.Button.RailIcon hover sets foreground=Wd.Accent.Cyan in addition to the brighter fill (rail buttons have icons, not text — color shift on the icon is the strongest signal).

5. Wd.Button.IsoToggle keeps its status-coded background on hover (LIVE cyan / ERROR coral / NO SIGNAL amber) so at-a-glance state isn't lost; instead bumps to a 2px cyan border for the affordance.

Verified: cleanly built, app launches, hover over Refresh button now has obvious brighter fill + tooltip vs. neighbors at rest.
2026-05-10 13:22:45 -04:00
63bd93d0c2 chore: sweep orphaned files (UpdateChecker, UpdateBanner, TeamsControlBridge, helper scripts)
Some checks failed
CI / build-and-test (push) Failing after 25s
2026-05-10 09:42:29 -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
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
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