diff --git a/DESIGN.md b/DESIGN.md index dd68092..609c2c1 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -1,7 +1,10 @@ # DESIGN.md — TeamsISO design system -Target framework: **WinUI 3 (Windows App SDK)**. Tokens are framework-agnostic; -the WinUI XAML implementation lives in `src/TeamsISO.App/Themes/` (post-port). +Target framework: **WPF .NET 8**. Tokens are framework-agnostic; the WPF +XAML implementation lives in `src/TeamsISO.App/Themes/`. (A WinUI 3 rebuild +was attempted and rolled back — see +`docs/shapes/2026-05-13-teamsiso-v2-studio-terminal.md` for the v2 shape +this design serves.) ## Color @@ -106,39 +109,49 @@ The toggle is also surfaced inside the settings drawer under an "Appearance" group as a tri-state pill (System / Dark / Light), so power users find it in the obvious place too. -### Implementation (WinUI 3) +### Implementation (WPF) -WinUI 3's `ThemeDictionary` pattern handles automatic swapping based on -`FrameworkElement.RequestedTheme`. Tokens live as `Color` resources keyed by -theme, then `SolidColorBrush` references them via `{ThemeResource}`: +WPF doesn't have WinUI 3's `ThemeDictionary` pattern. The equivalent is to +**split tokens by theme into separate ResourceDictionary files**, all +addressed via `DynamicResource` (NOT `StaticResource`) so the values can +be swapped at runtime. -```xml - - - - #0A0A0A - - - - #FAFAFB - - - - +``` +Themes/ + Theme.Tokens.xaml ← styles, control templates, key shape (no colors) + Theme.Dark.xaml ← color resources only — Dark variant + Theme.Light.xaml ← color resources only — Light variant ``` -At runtime, the root `Page.RequestedTheme = ElementTheme.Dark | Light` -switch propagates down the visual tree instantly — no app restart, no -flicker. The custom title bar (drawn via `AppWindow.TitleBar.ExtendsContent`) -gets a manual color update on the same code path since its system buttons -aren't part of the XAML tree. +`Theme.Dark.xaml` and `Theme.Light.xaml` define the SAME set of keys — +`Wd.Bg.Canvas`, `Wd.Accent.Cyan`, etc. — with different `Color` values. +`Theme.Tokens.xaml` references them via `DynamicResource` from styles and +templates. At startup, `App.xaml` merges `Theme.Tokens.xaml` plus exactly +one of `Theme.Dark.xaml` or `Theme.Light.xaml`. At runtime, `ThemeManager` +swaps the merged dictionary's color file: + +```csharp +var app = Application.Current; +var oldDict = app.Resources.MergedDictionaries + .First(d => d.Source?.OriginalString.EndsWith("Theme.Dark.xaml") == true + || d.Source?.OriginalString.EndsWith("Theme.Light.xaml") == true); +var idx = app.Resources.MergedDictionaries.IndexOf(oldDict); +app.Resources.MergedDictionaries[idx] = new ResourceDictionary { + Source = new Uri($"/Themes/Theme.{newTheme}.xaml", UriKind.Relative) +}; +``` + +`DynamicResource`-backed `SolidColorBrush` instances re-resolve on the +dictionary swap, so the visual tree repaints without an app restart. ### System mode -When `UIPreferences.Theme == System`, the app reads -`Application.Current.RequestedTheme` at startup and re-reads when the OS -theme changes (via `UISettings.ColorValuesChanged`). This is the default — -operators who don't care get whatever their Windows session is set to. +When `UIPreferences.Theme == "System"`, `ThemeManager` reads +`HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Themes\Personalize\AppsUseLightTheme` +at startup. It also subscribes to `SystemEvents.UserPreferenceChanged` so +the app re-resolves the theme when the operator flips Windows app-mode +mid-session. This is the default — operators who don't care get whatever +their Windows session is set to. ## Typography @@ -157,11 +170,12 @@ operators who don't care get whatever their Windows session is set to. Body text caps at 65–75ch where it wraps. Inline status text doesn't wrap — it truncates with ellipsis. -### Fonts in WinUI 3 +### Fonts in WPF -WPF's `pack://application:,,,/Assets/Fonts/#Inter` resource URI doesn't carry -over. WinUI 3 uses `ms-appx:///Assets/Fonts/Inter.ttf#Inter`. Migration is -mechanical but required. +Bundled fonts ship in `src/TeamsISO.App/Assets/Fonts/` and resolve via +`pack://application:,,,/Assets/Fonts/#Inter` / `#JetBrains Mono`. The +`` glob in `TeamsISO.App.csproj` already covers the `.ttf` files; +new font weights go in the same directory and pick up automatically. ## Spacing (8px grid) diff --git a/NEXT_STEPS.md b/NEXT_STEPS.md new file mode 100644 index 0000000..ec7bf84 --- /dev/null +++ b/NEXT_STEPS.md @@ -0,0 +1,89 @@ +# Where we left off — v2 "Studio Terminal" shell landed (2026-05-13 night) + +## What's done (uncommitted on local main) + +**v2 redesign shape:** Approved brief at `docs/shapes/2026-05-13-teamsiso-v2-studio-terminal.md`. Aesthetic register is "broadcast-engineering instrument" — Linear's keyboard-first density × Avid console legibility. Goes hard against the "screams AI" failure mode. + +**PRODUCT.md + DESIGN.md** updated to reflect WPF as the host (WinUI 3 lines removed), v2 IA decisions absorbed, recording references softened, theme implementation rewritten for WPF DynamicResource swap. + +**Theme system split:** +- `src/TeamsISO.App/Themes/Theme.Dark.xaml` — color brushes only, dark variant +- `src/TeamsISO.App/Themes/Theme.Light.xaml` — color brushes only, light variant +- `src/TeamsISO.App/Themes/WildDragonTheme.xaml` — styles + control templates (no color brushes anymore; uses `DynamicResource` for every brush ref) +- `src/TeamsISO.App/Services/ThemeManager.cs` — singleton that swaps the merged dictionary at runtime, reads `HKCU\...\AppsUseLightTheme` for System mode, subscribes to `SystemEvents.UserPreferenceChanged`, persists via `UIPreferences.Theme`. +- `App.xaml` merges Theme.Dark.xaml + WildDragonTheme.xaml by default; `App.xaml.cs.OnStartup` calls `ThemeManager.Current.Apply()` before MainWindow shows. +- `UIPreferences.Prefs` record gets a new `Theme = "System"` field (forward-compatible — old json files still load). +- New brush key: `Wd.Accent.CyanText` (Dark `#97EDF0` / Light `#0E7C82`) for cyan-as-text contrast on light canvas. The existing `Wd.Accent.Cyan` stays bright in both modes for fill use. + +**v2 main window shell:** +- Default Windows title bar (no more custom chromeless caption buttons — they looked generic and broke on DPI scaling). +- 32px header: Wild Dragon mark + "TeamsISO" wordmark left; three icon buttons right (⌘K command palette, theme toggle, settings gear). The mark is small (20px) as a quality cue — click opens About. +- 40px transport strip: mono-typed single line. `● 02:14:32 PART 4 · LIVE 2 CTRL :9755`. Cyan dot + timer only when at least one ISO live. CTRL cluster right-aligned in a Grid column. +- Body: alert banner + update banner + action toolbar (Enable / Refresh / Presets / Stop all + Teams launch/hide icons) + participants DataGrid. +- Conditional meeting bar at bottom — appears only when `IsTeamsInCall == true`, with Mute / Cam / Leave. +- 72px left rail: **gone**. +- 380px permanent settings panel: **gone**. +- Six-column footer: **gone**. +- Settings: slide-over drawer overlay (420px from right) triggered by the header gear; OUTPUT / NETWORK / APP tabs (DISPLAY renamed to APP); same bindings as v1. Scrim click dismisses; Esc dismisses. + +**Hotkeys preserved + new:** +- F1 help, Ctrl+R refresh, Ctrl+Shift+S panic stop, 1–9 / NumPad 1–9 toggle Nth participant — all preserved. +- **Ctrl+T toggle theme (NEW)** — cycles dark ↔ light. Hooked through `MainViewModel.ToggleThemeCommand` → `ThemeManager.Toggle()`. +- **Ctrl+K command palette (placeholder)** — currently opens the help dialog. Task 40 replaces this with a real fuzzy-search palette window. + +**View-model additions (MainViewModel):** +- `ParticipantCount` and `LiveCount` — feed the transport strip's "PART N · LIVE N" readout. Updated on the 1Hz stats tick. +- `ToggleThemeCommand` — wraps `ThemeManager.Current.Toggle()`. + +**MainWindow code-behind cleanup:** +- Removed `OnMinimize`, `OnMaximizeRestore`, `OnClose`, `OnWindowStateChanged`, the maximize-icon swap logic (no more custom title bar). +- Added `OnCommandPaletteClick`, `OnSettingsScrimClick`, `OnPreviewKeyDown` (Esc to close drawer). +- `OnSettingsToggleClick` now toggles `SettingsDrawerOverlay.Visibility` (the slide-over) instead of toggling the v1 right-column width. + +## To build, push, and demo + +```powershell +cd "C:\Users\zacga\Documents\Claude\Projects\Teams ISO" + +# Clear the corrupt local index from the earlier session +Remove-Item .git\index -Force -ErrorAction SilentlyContinue +git reset + +# Build the WPF host +dotnet build src\TeamsISO.App\TeamsISO.App.csproj -c Release + +# If the build is clean: +.\src\TeamsISO.App\bin\Release\net8.0-windows\TeamsISO.exe +``` + +In the running app, test: +1. **Theme toggle** — press `Ctrl+T`. Should swap dark ↔ light without restart. Header colors, surfaces, and text foregrounds all repaint. +2. **System theme follow** — open Windows Settings → Personalization → Colors → "Choose your mode" → flip between Dark and Light. TeamsISO should track the OS automatically (default preference is `System`). +3. **Settings drawer** — click the header gear icon. 420px drawer slides in from the right with OUTPUT / NETWORK / APP tabs. Esc or click-scrim dismisses. +4. **Transport strip** — should show the session timer when at least one ISO goes live, and the PART/LIVE counts always. +5. **Conditional meeting bar** — only appears when Teams is in a call. + +If anything regresses, the v1 shell is preserved in git history at `1d1ce6a` — easy rollback with `git reset --hard 1d1ce6a` then publish. + +## Commit + push when ready + +```powershell +git add -A src/TeamsISO.App/ docs/ PRODUCT.md DESIGN.md +git commit -m "feat(wpf): v2 'Studio Terminal' shell — theme system, header, transport strip, drawer + +- 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" +git push origin HEAD +``` + +## What's still queued (tasks 39 + 40) + +- **Task 39** — Participants table redesign. The DataGrid columns in the v2 shell are still v1-style (Name / Source / ISO toggle). v2 wants: 5 columns (state LED, name+codec, audio meter, output name mono, ISO pill), 52px rows, full-row active-speaker tint instead of left stripe, hard-edged 8px state LED. +- **Task 40** — Real Ctrl+K command palette window. Floating 560×360 dialog, fuzzy search across categories (Quick / Teams / Presets / Output / Network / App). Replaces the current placeholder that opens the help dialog. + +Both are scoped commits and depend on the v2 shell being confirmed working first. After this lands and you've verified the theme swap + drawer + transport strip render correctly, ping me and we'll knock those out. diff --git a/PRODUCT.md b/PRODUCT.md index 643d692..afb292d 100644 --- a/PRODUCT.md +++ b/PRODUCT.md @@ -15,14 +15,15 @@ OBS, Resolve, Ross, hardware capture), and does three things: 1. **Routes** each guest as a clean, individually-addressable, normalized NDI source (consistent framerate, resolution, aspect, audio routing — regardless of what each participant's webcam is doing). -2. **Records** every active ISO to disk simultaneously — raw BGRA + manifest - + ffmpeg convert script — so post-production gets a per-guest archive - ready to cut from. -3. **Orchestrates Teams itself** — launch/hide Teams windows, drive in-call +2. **Orchestrates Teams itself** — launch/hide Teams windows, drive in-call controls (mute, camera, share, leave, raise hand, quick-join) via UIAutomation, so the operator never has to alt-tab away from the routing table while the show is live. +(Recording — previously the second pillar — was removed in the WPF rollback +on 2026-05-13. The engine plumbing is intact for a future re-introduction, +but no UI surface, view-model command, REST route, or OSC route exposes it.) + External control surface (REST + WebSocket + OSC on localhost) lets a Companion / Stream Deck / TouchOSC controller drive routing remotely. @@ -113,9 +114,11 @@ behind purposeful entry points. ### 4. At-a-glance status is sacred. -Recording state, disk health, control-surface reachability, session timer, -NDI signal-per-participant — these are the operator's situational awareness. -They must be readable in peripheral vision, in one place, without scanning. +Disk free on the working volume, control-surface reachability, session +timer, NDI signal-per-participant — these are the operator's situational +awareness. They must be readable in peripheral vision, in one place, +without scanning. (Recording state was a historical fifth field; it's +removed.) ### 5. Confident neutrality over decorative warmth. @@ -148,11 +151,15 @@ read as AI-generated. Concretely, this means none of: ## Technical constraints (informing design) - Windows-only (Teams' NDI is Windows-only anyway). -- **WinUI 3 / Windows App SDK** is the new frontend target. +- **WPF .NET 8** is the supported frontend host. (A WinUI 3 rebuild was + attempted in May 2026; it proved fragile — XAML parser crashes on + DataTemplate, theme-glyph rendering issues — and was abandoned. The + rollback commit `1d1ce6a` is the canonical baseline.) - Engine layer (.NET 8) is preserved verbatim — view-model surface is the swap boundary. -- Bundled fonts via WinUI 3's `FontFamily` packaging (the WPF - `pack://...#Inter` resource URI doesn't translate; needs migration). +- Fonts are bundled via WPF's `pack://application:,,,/Assets/Fonts/#Inter` + resource URI so the operator's machine doesn't have to have Inter or + JetBrains Mono installed. - MSIX-signed installer is on the v1.0 path; the new shell needs to package cleanly through that pipeline. - The external control surface (REST/WebSocket on `:9755`, OSC on `:9000`) diff --git a/docs/shapes/2026-05-13-teamsiso-v2-studio-terminal.md b/docs/shapes/2026-05-13-teamsiso-v2-studio-terminal.md new file mode 100644 index 0000000..ea99298 --- /dev/null +++ b/docs/shapes/2026-05-13-teamsiso-v2-studio-terminal.md @@ -0,0 +1,194 @@ +# TeamsISO v2 — Studio Terminal (approved shape brief) + +**Date approved:** 2026-05-13 +**Approver:** Zac (operator + product owner) +**Host:** WPF .NET 8 (`src/TeamsISO.App/`). WinUI 3 rebuild is abandoned. +**Predecessor:** the WPF rollback at `1d1ce6a` (recording axed, settings pane tab fix, settings button wired). + +## Why this redesign + +The v1 GUI failed the "AI made that" test. Quote from the operator: "its cluttered, screams that AI made it - and relatively inefficient to navigate." The PRODUCT.md anti-references — card-grid-of-icons, always-visible side panel, footer-as-theatre — all describe the current build. v2 commits to a different aesthetic register entirely. + +## Aesthetic register + +**Broadcast-engineering instrument.** Not a SaaS dashboard. Not Material. Not Fluent default. + +Reference proximity: Linear's keyboard-first density × Avid S6 console legibility × Blackmagic ATEM's information hierarchy. The operator mental model is "I'm sitting at an audio mixer; every region has a job, no region is theatre." + +## What goes away + +- The 72px left rail (no actual navigation — there's only one screen) +- The 380px always-visible settings pane (settings change rarely, shouldn't claim permanent real estate) +- The 6-column footer status row (theatre, not information) +- The custom chromeless title-bar caption buttons (look worse than system chrome, break on DPI scaling) +- The "by Wild Dragon" pill and the always-visible "TeamsISO" wordmark as decorative chrome +- The in-call control bar as a permanent strip (only relevant in-call; should appear conditionally) +- The seven identical ghost buttons in the in-call bar (textbook card-grid anti-pattern) + +## What replaces it + +``` +┌─ system Windows title bar [_ □ ✕] ─────────────────────┐ +│ 🐉 TeamsISO [⌘K] [☾] [⚙] │ 32px header — mark + wordmark left, 3 icons right +├────────────────────────────────────────────────────────┤ +│ ● 02:14:32 PART 4 · LIVE 2 DISK 482g CTRL :9755 │ transport strip — single mono line, replaces footer +├────────────────────────────────────────────────────────┤ +│ │ +│ ▮ alice ▮▮▮▮ t:5ms alice [LIVE] │ +│ ▯ bob ▮▮ t:8ms bob [— OFF]│ participants table = the canvas +│ ▮ carlos ▮▮▮▮▮ t:9ms carlos [LIVE] │ (cyan-tinted row bg = active speaker) +│ ▮ guest 4 -- NO SIG guest_4 [ERROR]│ +│ │ +├────────────────────────────────────────────────────────┤ +│ IN CALL · Daily standup [mute] [cam] [leave] │ conditional — only renders when in call +└────────────────────────────────────────────────────────┘ +``` + +### Header (32px) + +Left: Wild Dragon mark (~20px) + "TeamsISO" wordmark in Inter 13 Medium. Click on mark opens About. +Right: three icon buttons. +- `⌘K` (Tabler `ti-command`) — opens command palette (also Ctrl+K, Ctrl+P shortcut) +- `☾` / `☀` (Tabler `ti-moon` / `ti-sun`) — cycles theme dark ↔ light. Tooltip "Theme (System / Dark / Light)" — long-press could open the tri-state, but for v2 just a one-click cycle. +- `⚙` (Tabler `ti-settings`) — opens settings drawer + +That's all the chrome. No nav rail because there's nothing to navigate to. + +### Transport strip + +Single horizontal line. Mono type (JetBrains Mono 12). Replaces the entire footer. + +Fields: +- `● 02:14:32` — green dot + session timer when at least one ISO is live; both hidden otherwise +- `PART 4 · LIVE 2` — participant count and live-ISO count; "PART" / "LIVE" in Inter 11 SemiBold UPPER tracking 0.06em, numbers in mono +- `DISK 482g` — free disk space on the working volume; coral text if <10GB, hidden if no relevant volume is configured +- `CTRL :9755` — control surface bind; cyan text when active, hidden when off + +No icons. No badges. No backgrounds. Just typed status — a console heads-up display. + +### Participants table — the canvas + +Five columns: + +| # | Width | Content | Type | +|---|---|---|---| +| 1 | 24px | State LED — 8×8 filled cyan/coral or hollow neutral | hard-edged square, no rounding | +| 2 | * | Name (Inter 13/Medium) + codec/latency caption (Mono 11/Regular, tertiary fg) | "Alice Wong" / "NDIV5 · t:5ms" | +| 3 | 110px | Audio meter — 5 vertical bars, instantaneous level | hard-edged, cyan when LIVE, neutral when OFF | +| 4 | 130px | Output name | Mono 12 | +| 5 | 100px | ISO toggle pill | LIVE = cyan fill / OFF = hollow neutral / ERROR = coral outline | + +Row height: 52px (was 56). + +Active speaker: full-row background tint `bg.active-speaker` (cyan-tinted muted neutral). NOT a left-edge stripe — that trips the impeccable "side-stripe border" ban. + +Each row reacts to: +- Click anywhere → focuses the row, keyboard-actions apply +- Click the pill → toggle ISO +- Right-click → context menu (preview, custom name, copy NDI source name, save snapshot) +- Hover → reveals a kebab affordance in column 5 right edge for less-frequent actions + +### Conditional meeting bar + +Renders below the table only when `TeamsControlBridge.DetectCallState().IsInCall == true`. Slides up from below on transition (~120ms ease-out-quart on `RenderTransform.Y` + `Opacity`). + +Content: `IN CALL` label (Inter 11 SemiBold UPPER, cyan accent) + meeting title (Mono 12, truncated with ellipsis) + three buttons right-aligned (Mute / Cam / Leave). Share and Notes do NOT live here — they move to ⌘K, where they're invocable any time without the bar fighting for attention. + +Width matches the table — not full-bleed; respects the page padding. + +### Ctrl+K command palette + +The redesign's navigation move. Replaces ~80% of what's in the v1 rail + tabbed settings. + +Behavior: +- `Ctrl+K` (also `Ctrl+P`) opens a centered floating window over the main shell, 560×360px +- Search input at the top, results list below +- Empty input → frequent + recent commands +- Typing → fuzzy-matches across command label + category + keywords +- ↑/↓ navigates, Enter invokes, Esc closes + +Command categories (each command has icon, label, optional value preview, optional shortcut hint): +- **Quick** — Enable all online, Stop all ISOs, Refresh discovery, Drop snapshot of all +- **Teams** — Launch Teams, Hide / show Teams windows, Mute, Toggle camera, Open share, Leave call +- **Presets** — Apply … (one row per saved preset), Save current as preset, Manage presets +- **Output** — Framerate 24 / 30 / 60, Resolution 1080p / 720p, Aspect Pillarbox / Letterbox / Stretch +- **Network** — Apply transcoder topology, Restore default NDI groups, Edit output name template +- **App** — Theme dark / light / system, Open settings, About TeamsISO, Help (F1) + +This is the keyboard-first surface broadcasters with Stream Decks already mentally use. + +### Settings — slide-over drawer + +Triggered from the header gear icon, or from `Open settings` in the palette, or hotkey `,` (comma). + +- 420px wide, slides in from the right +- 40% canvas scrim behind +- Three tabs: **OUTPUT** (framerate / resolution / aspect / audio + Reset to defaults), **NETWORK** (discovery / output groups + Apply transcoder topology + Restore defaults + output name template), **APP** (theme tri-state, minimize to tray, sort order, Launch Teams on startup, Auto-hide Teams windows) +- Apply Changes button pinned to drawer footer; Esc dismisses; click outside the drawer dismisses + +DISPLAY tab from v1 gets renamed APP and absorbs the theme tri-state. + +### Empty states + +- No participants yet: a single centered mono sentence, "no ndi sources yet — open teams and start a meeting", and one tertiary button "Refresh discovery (Ctrl+R)". No illustration, no mascot. +- Not in a call: meeting bar simply doesn't render. No placeholder. +- Discovery degraded: amber dot in transport strip's session timer position, mono text "NDI discovery — restarting". No banner. + +## Color, theme, motion + +**Color strategy:** Restrained (impeccable product default). Cyan accent earns its place — reserved for LIVE state, focus ring, active speaker tint. Coral reserved for destructive + error. Status amber for warnings. Green NOT used (would compete with cyan for "ok / live" semantics). + +**Theme default:** Follow Windows. Theme persists per-operator via `UIPreferences.Theme`. Implementation: split `WildDragonTheme.xaml` into a single style + token-shape file plus two color-only ResourceDictionary files (`Theme.Dark.xaml`, `Theme.Light.xaml`). At runtime `ThemeManager` swaps the merged dictionary entry. WPF analog of WinUI's `ThemeDictionary`. + +**Motion:** +- 120ms `cubic-bezier(0.16, 1, 0.3, 1)` (ease-out-quart) on the meeting bar slide-in/out +- 200ms ease-out on the drawer slide +- 180ms cross-fade on theme swap +- 90ms on focus + hover transitions +- No bounce, no elastic, no spring overshoots. Animate `RenderTransform` and `Opacity` only — never layout properties. + +## Typography commitments + +| Token | Family | Size | Weight | Used for | +|---|---|---|---|---| +| `text.timer` | JetBrains Mono | 14 | Medium | Session timer in transport strip — instrument-grade | +| `text.caption` | Inter | 11 | SemiBold (600) | UPPER + tracking 0.06em — transport-strip labels, "IN CALL", "SPEAKING" | +| `text.display` | Inter | 22 | SemiBold | Settings drawer headings only | +| `text.title` | Inter | 13 | Medium | Wordmark, table column headers | +| `text.body` | Inter | 13 | Regular | Participant display names | +| `text.mono.code` | JetBrains Mono | 12 | Regular | Output names, NDI IDs, meeting title | +| `text.mono.tech` | JetBrains Mono | 11 | Regular | Latency readouts, codec captions, transport-strip values | + +## What this is NOT + +- Not Fluent-styled. Default Fluent accent integration is generic Windows; TeamsISO is a broadcaster's tool. +- Not minimalism for its own sake. The participants table is *dense*. Density is the broadcaster's virtue. +- Not chromeless. Default system title bar stays. Chromeless windows break embarrassingly at 4K + DPI scaling. +- Not vanity-branded. The Wild Dragon mark sits small in the header as a quality cue, never as decoration. + +## Migration path + +The view-model surface in `src/TeamsISO.App/ViewModels/` is the contract. The redesign rewrites `MainWindow.xaml` and `Themes/*` but leaves view-models, the engine, the control surface server, and the OSC bridge untouched. + +Order of operations (each step builds clean before the next): + +1. **Theme split** — Refactor `WildDragonTheme.xaml` → `Themes/Theme.Tokens.xaml` (styles + key shape) + `Themes/Theme.Dark.xaml` + `Themes/Theme.Light.xaml` (color resources only). Port `ThemeManager` from the deleted WinUI project; wire system app-mode detection via registry (`HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Themes\Personalize\AppsUseLightTheme`). +2. **Main window shell** — Replace MainWindow.xaml's outer Grid. Add 32px header, transport strip, full-width content area, conditional meeting bar. Delete the 72px rail, the 380px right pane, the footer. +3. **Participants table redesign** — 5 columns, LED state, instantaneous audio meter, ISO pill. +4. **Settings drawer** — Slide-over from right, dismissable; reuses existing settings view-model. +5. **Command palette** — `Ctrl+K` floating window with fuzzy command list. + +Each step is a self-contained commit so the v1 build remains shippable at any rollback point. + +## Anti-references — explicit on the "AI made that" failure + +These are the failure modes the redesign defends against: +- Card-grid-of-icons (the v1 in-call bar's seven identical ghost buttons) +- Always-visible side panel (the v1 380px settings sidebar) +- Decorative chrome (the v1 "by Wild Dragon" pill, the 72px nav rail, the six-column footer) +- Generic Inter at 13 for everything +- Default WPF DataGrid (Excel) +- Custom chromeless title bars that look generic +- Gradient text, glassmorphism, side-stripe borders (impeccable absolute bans) +- "Hero metric + supporting stats + gradient" SaaS dashboards +- Mascots, "Welcome!" copy, illustrated onboarding cards diff --git a/src/TeamsISO.App/App.xaml b/src/TeamsISO.App/App.xaml index 0c6fdab..76d6e06 100644 --- a/src/TeamsISO.App/App.xaml +++ b/src/TeamsISO.App/App.xaml @@ -4,6 +4,18 @@ + + diff --git a/src/TeamsISO.App/App.xaml.cs b/src/TeamsISO.App/App.xaml.cs index 309cb25..e2b0837 100644 --- a/src/TeamsISO.App/App.xaml.cs +++ b/src/TeamsISO.App/App.xaml.cs @@ -76,6 +76,12 @@ public partial class App : Application DispatcherUnhandledException += OnDispatcherUnhandled; System.Threading.Tasks.TaskScheduler.UnobservedTaskException += OnUnobservedTaskException; + // Resolve and apply the theme BEFORE any window is shown so we don't + // paint a dark frame for one tick then flip to light (or vice versa). + // ThemeManager.Apply swaps Application.Resources.MergedDictionaries + // in place; DynamicResource refs in WildDragonTheme.xaml re-bind. + TeamsISO.App.Services.ThemeManager.Current.Apply(); + // Single-instance gate: if another TeamsISO is already running for this user, // broadcast the bring-to-front message and exit silently. This prevents the // NDI/config contention seen during testing where two finders, two senders diff --git a/src/TeamsISO.App/MainWindow.xaml b/src/TeamsISO.App/MainWindow.xaml index 31ce6c0..93668c7 100644 --- a/src/TeamsISO.App/MainWindow.xaml +++ b/src/TeamsISO.App/MainWindow.xaml @@ -1,60 +1,54 @@ - + SnapsToDevicePixels="True" + TextOptions.TextRenderingMode="ClearType" + TextOptions.TextFormattingMode="Display" + d:DataContext="{d:DesignInstance Type=vm:MainViewModel}"> - - - - - + - - - - + + + @@ -76,159 +70,221 @@ - - - - - - + + + + + + - - + - + BorderThickness="0,0,0,1"> + + + + + + - - + + + - - - - - - - + - - - - - - - - - - - - - - - + Width="14" Height="14" + Stretch="None"/> + + + - - + + + + + + + + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Margin="0,0,12,0"/> + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +