From dc63cffc0587abe6680d873f426ead8ddc116b71 Mon Sep 17 00:00:00 2001 From: opariffazman Date: Fri, 26 Jun 2026 01:15:31 +0800 Subject: [PATCH 1/9] docs: WinUI control-panel cleanup design spec --- ...6-26-winui-control-panel-cleanup-design.md | 136 ++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 docs/superpowers/specs/2026-06-26-winui-control-panel-cleanup-design.md diff --git a/docs/superpowers/specs/2026-06-26-winui-control-panel-cleanup-design.md b/docs/superpowers/specs/2026-06-26-winui-control-panel-cleanup-design.md new file mode 100644 index 0000000..0fc031f --- /dev/null +++ b/docs/superpowers/specs/2026-06-26-winui-control-panel-cleanup-design.md @@ -0,0 +1,136 @@ +# WinUI Control-Panel Cleanup — Design + +**Date:** 2026-06-26 +**Branch:** `worktree-feature+winui-panel-cleanup` (worktree, from origin/main) + +## Context + +The WinUI control panel (`MainWindow.xaml`) is a single flat `ScrollViewer → StackPanel`: +camera, AI-effect toggles/sliders, overlay-behavior toggles, Start/Stop, and the status line +are all jammed together with no grouping or hierarchy. The window uses the default WinUI title +bar and a hardcoded 400×720 size that leaves the panel cramped or empty depending on DPI. Two +controls (Lock overlay, Click-through) and one (Zoom) are not meaningful for this app's intent — +a draggable, always-on-top camera overlay. The non-obvious overlay gestures (scroll-to-resize, +drag-the-handle) are undiscoverable. + +Goal: streamline the panel for **clarity and accessibility** — group controls by concern, add a +custom branded title bar, surface the hidden gestures, drop the controls that don't belong, and +size the window to its content instead of a magic number. + +## Scope + +UI-layer change. C# shim, capture pipeline, and Maxine code are untouched. + +### Removed +- **Lock overlay** toggle + VM `Locked` property + config load/save + `OnVmPropertyChanged` branch + `SetLocked` call. +- **Click-through** toggle + VM `ClickThrough` property + config load/save + branch + `SetClickThrough` call + the `ToggleClickThrough` default hotkey binding. +- **Zoom** slider + VM `Zoom` property + branch + `SetZoom` call. + +Removing `Locked` and `ClickThrough` makes `OverlayWindow.IsInteractive` (`!_locked && !_clickThrough`) +always `true` — the overlay is permanently draggable/resizable, matching intent. `SetZoom` is no +longer called, so `UpdateScale`'s `Matrix3x2` uses zoom = 1 (mirror-only transform). + +**Deliberately kept (ponytail):** `OverlaySettings.Locked/ClickThrough/Zoom` fields and the +`HotkeyAction.ToggleLock/ToggleClickThrough` enum members stay (unused), marked with a `ponytail:` +comment. Removing them would cascade into config-versioning + 3 test files for no user benefit. +`OverlayWindow.SetLocked/SetClickThrough/SetZoom` stay on the type (now unused by the app); they're +harmless and the overlay defaults (`_locked=false`, `_clickThrough=false`, zoom=1) give the desired +always-interactive, no-zoom behavior with no call needed. + +### Kept / Moved +- **Mirror** → folded into the CAMERA group. +- **Lock exposure + Exposure slider** → CAMERA group (camera-related, not AI). +- Camera combo, Start/Stop, status line → CAMERA group. +- Green Screen (+Expand/Feather), Eye Contact (+note), AI Sharpness (+Quality, +note) → AI EFFECTS group. + +## Layout + +``` +🎥 Camera on Screen ─ □ ✕ ← custom title bar +───────────────────────────────────────────────── + ⓘ Scroll over the overlay to resize · ← InfoBar (Informational, IsClosable=False) + drag the + handle to move it. + + CAMERA ← section header (BodyStrongTextBlockStyle) + ┌───────────────────────────────────────────┐ ← card: Border, CardBackgroundFillColorDefaultBrush + │ Camera [Logitech ▾] │ + │ [ ▶ Start ] [ ⏹ Stop ] • Running 30fps│ + │ Mirror (selfie view) [ON ●] │ + │ Lock exposure (steady FPS) [OFF] │ + │ Exposure ──●──── │ + └───────────────────────────────────────────┘ + + AI EFFECTS + ┌───────────────────────────────────────────┐ + │ AI Green Screen [ON ●] │ + │ Expand ──●── Feather ──●── │ + │ Eye Contact [OFF] │ + │ (caution note when unavailable) │ + │ AI Sharpness [Denoise ▾] Quality [High ▾] │ + │ (caution note when unavailable) │ + └───────────────────────────────────────────┘ +───────────────────────────────────────────────── + NVIDIA Maxine attribution (pinned footer — unchanged) +``` + +## Components + +### 1. Custom title bar +- `ExtendsContentIntoTitleBar = true` (in `MainWindow` ctor) + `SetTitleBar()`. +- Title bar = a `Grid` (row 0 of the window) holding the app icon (cos.ico, 16×16 `Image`) + + "Camera on Screen" `TextBlock` (`CaptionTextBlockStyle`), left-aligned, height 32, with the + draggable region excluding the caption-button inset. +- System caption buttons (min/max/close) and system theme — no custom button drawing. +- Verify the exact API surface (`AppWindowTitleBar`, drag-region, RTL inset) via context7 / winui-design before coding. + +### 2. Grouped cards (native, no new dependency) +- Each group: a `TextBlock` header (`BodyStrongTextBlockStyle`) + a `Border` + (`Background={ThemeResource CardBackgroundFillColorDefaultBrush}`, `CornerRadius=8`, + `BorderBrush={ThemeResource CardStrokeColorDefaultBrush}`, padding 12) wrapping a `StackPanel` + of the existing controls. No `Expander` (small panel, everything stays visible = clarity). +- Controls keep their existing `Header` attribute — that is the accessibility name. Add + `AutomationProperties.Name` only where a control has no `Header` (e.g. the Start/Stop buttons get + explicit names; the status `TextBlock` is `AutomationProperties.LiveSetting=Polite`). +- Decision: native cards over `CommunityToolkit.WinUI.Controls.SettingsControls` — only 2 groups, + a NuGet dep isn't justified. + +### 3. Help InfoBar +- `InfoBar Severity="Informational" IsOpen="True" IsClosable="False"` at the top of the scroll body. +- Message states both hidden gestures: "Scroll over the overlay to resize it · drag the + handle to move it." + +### 4. Dynamic sizing +- Drop the hardcoded `PanelWidthDip/HeightDip = 400/720`. +- After first layout, measure the root content's `DesiredSize` and `AppWindow.Resize` to it + (DPI-scaled, as `RightSizePanel` already does). Set a **minimum** window size to that measured + size so every control stays reachable; window remains user-resizable, `ScrollViewer` is the + fallback when shrunk. +- Min-size enforcement: use `OverlappedPresenter.PreferredMinimumWidth/Height` if available in + WinAppSDK 1.8; else hook `WM_GETMINMAXINFO`. Confirm the API via context7 before coding. + +## Data flow / contracts + +No contract changes. `BuildParams()` still feeds the shim; removing Zoom/Lock/ClickThrough only +removes their `On…Changed` → `ApplyLiveParams` / overlay-setter wiring. `ToAppConfig` writes the +kept config fields with the removed ones at their defaults. + +## Error handling + +Unchanged. Capability probe still gates AI toggles OneWay; caution notes still bind to +`CapabilityDetail` / `EyeContactDetail` visibility. + +## Testing + +- **Core unit tests** (`dotnet test`) must stay green. `MainViewModelTests` references + `Locked/ClickThrough/Zoom` — update those assertions to the removed-property reality (the props + are gone from the VM; config round-trip keeps the fields at default). This is the one runnable + check that fails if the VM surgery is wrong. +- **App build** must be pristine (0 warnings) — but the App project can't be built on a host + without the shim DLL + Windows App SDK runtime; CI (self-hosted RTX) is the build gate. +- **Visual confirmation is a human gate** (per repo convention): the panel layout, title bar, and + InfoBar can only be eyeballed by running the app on the RTX host. Document what to look for. + +## Out of scope +- No theme/brand-accent color (system theme only). +- No SettingsControls toolkit. +- No removal of the kept-but-unused config fields / enum members / overlay setters. +- No overlay-window (capture) changes. From 298f403d8f47b0a3251d573ed971d5018373c488 Mon Sep 17 00:00:00 2001 From: opariffazman Date: Fri, 26 Jun 2026 01:21:58 +0800 Subject: [PATCH 2/9] docs: WinUI control-panel cleanup implementation plan --- .../2026-06-26-winui-control-panel-cleanup.md | 491 ++++++++++++++++++ 1 file changed, 491 insertions(+) create mode 100644 docs/superpowers/plans/2026-06-26-winui-control-panel-cleanup.md diff --git a/docs/superpowers/plans/2026-06-26-winui-control-panel-cleanup.md b/docs/superpowers/plans/2026-06-26-winui-control-panel-cleanup.md new file mode 100644 index 0000000..e686702 --- /dev/null +++ b/docs/superpowers/plans/2026-06-26-winui-control-panel-cleanup.md @@ -0,0 +1,491 @@ +# WinUI Control-Panel Cleanup Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Streamline the WinUI control panel — grouped cards, custom title bar, a gesture-help InfoBar, drop Lock/Click-through/Zoom, and size the window to its content. + +**Architecture:** UI-layer only. Core VM loses three observable props (Lock/ClickThrough/Zoom); `MainWindow.xaml` is regrouped into two `Border` cards under section headers with a custom title bar; the capture/shim/overlay pipeline is untouched. Removed config fields and enum members are kept (unused) to avoid config-version churn. + +**Tech Stack:** C# .NET 8, WinUI 3 (Windows App SDK 1.8), CommunityToolkit.Mvvm, xUnit. + +## Global Constraints + +- **Pristine build:** 0 warnings (CI enforces `/warnaserror` + `TreatWarningsAsErrors`). No `#pragma warning disable`. +- **No new NuGet dependency** — native `Border`/`TextBlock`/`InfoBar` only, not the SettingsControls toolkit. +- **System theme only** — no brand-accent brushes; bind to `ThemeResource` keys. +- **Core has no WinUI/Win32 types** — VM changes stay pure .NET. +- **Keep, do not remove:** `OverlaySettings.{Locked,ClickThrough,Zoom}` fields; `HotkeyAction.{ToggleLock,ToggleClickThrough}` enum members; `OverlayWindow.{SetLocked,SetClickThrough,SetZoom}` methods. Mark deliberate keeps with a `// ponytail:` comment. +- **Maxine attribution footer stays** (SDK License Supplement §3.1) — pinned, always visible. +- **WinUI API verification:** before coding the title bar and min-size (Task 4/5), consult Context7 MCP (`resolve-library-id` → `query-docs`) for the current Windows App SDK 1.8 `AppWindowTitleBar` / `OverlappedPresenter` surface, per the global Context7 rule. Code below is the expected shape; confirm signatures before relying on them. +- **App-project verification is a CI/human gate:** the App can't build on this host (needs the shim DLL + RTX). Tasks 3–5 are verified by the self-hosted RTX CI build (0 warnings) plus a visual human check; only Tasks 1–2 (Core) run locally via `dotnet test`. + +--- + +### Task 1: Remove Lock/ClickThrough/Zoom from the view-model + +**Files:** +- Modify: `src/CameraOnScreen.Core/ViewModels/MainViewModel.cs` +- Test: `tests/CameraOnScreen.Core.Tests/ViewModels/MainViewModelTests.cs` + +**Interfaces:** +- Consumes: `AppConfig`, `OverlaySettings` (unchanged — fields stay). +- Produces: `MainViewModel` with no `Locked`/`ClickThrough`/`Zoom` properties; `Mirror` kept. `ToAppConfig` still returns a fully-populated `AppConfig` (removed fields default: `Locked=false`, `ClickThrough=false`, `Zoom=1.0`). + +- [ ] **Step 1: Update the VM tests to the new contract** + +In `MainViewModelTests.cs`, **replace** the test `LoadFrom_propagates_locked_and_clickthrough` (currently ~lines 149-160) and `LoadFrom_propagates_mirror_and_zoom` (~196-207) with a single mirror-only test, and **edit** `ToAppConfig_captures_geometry_and_effects` (~162-176) and `ToAppConfig_captures_mirror_and_zoom` (~209-218) to drop Lock/Zoom. + +Replace the two `LoadFrom_*` tests with: + +```csharp + [Fact] + public void LoadFrom_propagates_mirror() + { + var vm = Build(GpuTier.Rtx, out _); + var config = new AppConfig + { + Overlay = new OverlaySettings { Mirror = true } + }; + vm.LoadFrom(config); + Assert.True(vm.Mirror); + } +``` + +Replace `ToAppConfig_captures_geometry_and_effects` body's Lock lines — set it to: + +```csharp + [Fact] + public void ToAppConfig_captures_geometry_and_effects() + { + var vm = Build(GpuTier.Rtx, out _); + vm.GreenScreenEnabled = true; + var cfg = vm.ToAppConfig(10, 20, 300, 400); + Assert.Equal(10, cfg.Overlay.X); + Assert.Equal(20, cfg.Overlay.Y); + Assert.Equal(300, cfg.Overlay.Width); + Assert.Equal(400, cfg.Overlay.Height); + Assert.True(cfg.Effects.GreenScreenEnabled); + Assert.Null(cfg.CameraId); + } +``` + +Replace `ToAppConfig_captures_mirror_and_zoom` with mirror-only: + +```csharp + [Fact] + public void ToAppConfig_captures_mirror() + { + var vm = Build(GpuTier.Rtx, out _); + vm.Mirror = true; + var cfg = vm.ToAppConfig(10, 20, 300, 400); + Assert.True(cfg.Overlay.Mirror); + } +``` + +- [ ] **Step 2: Run the tests to verify they fail to compile** + +Run: `dotnet test tests/CameraOnScreen.Core.Tests/CameraOnScreen.Core.Tests.csproj` +Expected: BUILD FAILURE — tests still reference `vm.Locked`/`vm.Zoom` nowhere now, but the VM still *defines* them; compile actually still succeeds and tests PASS. (This is a deletion refactor — the suite is the regression guard, not a red-first test.) Proceed to Step 3. + +- [ ] **Step 3: Remove the three observable properties** + +In `MainViewModel.cs`, delete these three `[ObservableProperty]` lines (~76-79), keeping `mirror`: + +```csharp + [ObservableProperty] private bool locked; // DELETE + [ObservableProperty] private bool clickThrough; // DELETE + [ObservableProperty] private double zoom = 1.0; // DELETE +``` + +Keep: +```csharp + [ObservableProperty] private bool mirror; +``` + +- [ ] **Step 4: Remove the load/save lines** + +In `LoadFrom` (~106-109), delete the `Locked`, `ClickThrough`, and `Zoom` lines; keep `Mirror`: + +```csharp + // DELETE: Locked = config.Overlay.Locked; + // DELETE: ClickThrough = config.Overlay.ClickThrough; + Mirror = config.Overlay.Mirror; + // DELETE: Zoom = config.Overlay.Zoom; +``` + +In `ToAppConfig`'s `OverlaySettings` initializer (~121-126), drop `Locked`/`ClickThrough`/`Zoom` so they take their record defaults: + +```csharp + Overlay = new OverlaySettings + { + X = x, Y = y, Width = w, Height = h, + // ponytail: Locked/ClickThrough/Zoom intentionally omitted — fields kept on the record + // (default false/false/1.0) so config stays schema-stable; the overlay is always + // interactive and unzoomed now. + Mirror = Mirror + }, +``` + +Also update the stale comment above `ToAppConfig` (~116-117) that says "Locked/ClickThrough are the existing observable props" — replace with: `// Mirror is the kept observable prop; Lock/ClickThrough/Zoom were removed (overlay is always interactive).` + +- [ ] **Step 5: Run the tests to verify green** + +Run: `dotnet test tests/CameraOnScreen.Core.Tests/CameraOnScreen.Core.Tests.csproj` +Expected: PASS — 58 tests, 0 failures (was 59; net -1 after merging two load tests into one). + +- [ ] **Step 6: Commit** + +```bash +git add src/CameraOnScreen.Core/ViewModels/MainViewModel.cs tests/CameraOnScreen.Core.Tests/ViewModels/MainViewModelTests.cs +git commit -m "refactor(vm): drop Lock/ClickThrough/Zoom from view-model" +``` + +--- + +### Task 2: Drop the dead default hotkeys + +**Files:** +- Modify: `src/CameraOnScreen.Core/Config/Models.cs:53-59` +- Test: `tests/CameraOnScreen.Core.Tests/Hotkeys/HotkeyValidatorTests.cs` (no change expected — verify) + +**Interfaces:** +- Consumes: nothing new. +- Produces: `AppConfig.DefaultHotkeys()` returns 2 bindings (`ToggleOverlayVisible`, `ToggleRunning`). The `HotkeyAction.ToggleLock`/`ToggleClickThrough` enum members remain (still referenced by `HotkeyValidatorTests`). + +- [ ] **Step 1: Remove the two dead default bindings** + +In `Models.cs`, `DefaultHotkeys()` — delete the `ToggleLock` and `ToggleClickThrough` entries (~55-56), keeping the other two: + +```csharp + // VK codes: F10=0x79, F11=0x7A. (ToggleLock/ToggleClickThrough default bindings removed — + // ponytail: enum members kept for config compatibility, but no UI/behavior toggles them now.) + public static IReadOnlyList DefaultHotkeys() => Array.AsReadOnly(new[] + { + new HotkeyBinding { Action = HotkeyAction.ToggleOverlayVisible,Modifiers = HotkeyModifiers.Control | HotkeyModifiers.Alt, VirtualKey = 0x79 }, + new HotkeyBinding { Action = HotkeyAction.ToggleRunning, Modifiers = HotkeyModifiers.Control | HotkeyModifiers.Alt, VirtualKey = 0x7A }, + }); +``` + +- [ ] **Step 2: Run the Core tests** + +Run: `dotnet test tests/CameraOnScreen.Core.Tests/CameraOnScreen.Core.Tests.csproj` +Expected: PASS, 0 failures. `HotkeyValidatorTests.DefaultHotkeys_has_no_conflict` still holds (two bindings, distinct keys); the conflict test still compiles (`ToggleLock` enum member intact). + +- [ ] **Step 3: Commit** + +```bash +git add src/CameraOnScreen.Core/Config/Models.cs +git commit -m "refactor(config): drop dead Lock/ClickThrough default hotkeys" +``` + +--- + +### Task 3: Strip removed wiring from the window code-behind + +**Files:** +- Modify: `src/CameraOnScreen.App/MainWindow.xaml.cs` + +**Interfaces:** +- Consumes: `MainViewModel` without `Locked`/`ClickThrough`/`Zoom` (Task 1). +- Produces: a code-behind that references only `Mirror` among the removed props; the overlay is left at its always-interactive defaults (`_locked=false`, `_clickThrough=false`, `_zoom=1.0` — no setter calls). + +> Not locally buildable (App needs shim + RTX). Verified at CI build + visual gate. + +- [ ] **Step 1: Remove the SetLocked/SetClickThrough/SetZoom ctor calls** + +In the ctor (~57-61), delete the `SetLocked`, `SetClickThrough`, and `SetZoom` lines; keep `SetMirror`: + +```csharp + // Apply the initial state loaded from config to the overlay. + _overlay.SetMirror(Vm.Mirror); + // ponytail: overlay stays always-interactive (no SetLocked/SetClickThrough) and unzoomed + // (no SetZoom) — Lock/ClickThrough/Zoom were removed from the panel. +``` + +- [ ] **Step 2: Remove the hotkey switch cases for Lock/ClickThrough** + +In `OnHotkeyAction` (~173-181), delete the `ToggleLock` and `ToggleClickThrough` cases (they referenced the removed `Vm.Locked`/`Vm.ClickThrough`), keeping the other two: + +```csharp + switch (action) + { + case HotkeyAction.ToggleOverlayVisible: _overlay.ToggleVisible(); break; + case HotkeyAction.ToggleRunning: + if (Vm.IsRunning) Vm.StopCommand?.Execute(null); else Vm.StartCommand?.Execute(null); + break; + // ToggleLock/ToggleClickThrough: no-ops now (overlay always interactive); enum kept. + } +``` + +- [ ] **Step 3: Remove the OnVmPropertyChanged branches for Locked/ClickThrough/Zoom** + +In `OnVmPropertyChanged` (~287-294), delete the three `else if` branches for `Locked`, `ClickThrough`, and `Zoom`; keep the `Mirror` branch: + +```csharp + else if (e.PropertyName == nameof(MainViewModel.Mirror)) + _overlay.SetMirror(Vm.Mirror); + // Locked/ClickThrough/Zoom branches removed — those props no longer exist. +``` + +- [ ] **Step 4: Commit** + +```bash +git add src/CameraOnScreen.App/MainWindow.xaml.cs +git commit -m "refactor(app): remove Lock/ClickThrough/Zoom overlay wiring" +``` + +--- + +### Task 4: Custom title bar + dynamic sizing in code-behind + +**Files:** +- Modify: `src/CameraOnScreen.App/MainWindow.xaml.cs` + +**Interfaces:** +- Consumes: an `x:Name="AppTitleBar"` element added in Task 5's XAML, and `x:Name="RootGrid"` (the window root). +- Produces: `ExtendsContentIntoTitleBar=true` + `SetTitleBar(AppTitleBar)`; window sized to content on first layout instead of the hardcoded 400×720. + +> **Before coding:** Context7 `query-docs` for "Windows App SDK 1.8 ExtendsContentIntoTitleBar SetTitleBar AppWindow Resize OverlappedPresenter minimum size". Confirm `SetTitleBar(UIElement)` and whether `OverlappedPresenter.PreferredMinimumWidth/Height` exist in 1.8. The code below is the expected shape. + +- [ ] **Step 1: Replace `RightSizePanel` with size-to-content** + +Delete the `PanelWidthDip`/`PanelHeightDip` consts (~15) and rewrite `RightSizePanel` (~119-127) to measure the laid-out content and resize to it. Call it from the existing ctor call site (`RightSizePanel();` ~74) but defer to first layout via `RootGrid.Loaded`: + +```csharp + // Size the window to its content (DPI-scaled) instead of a magic number, so every control is + // visible at open. ScrollViewer is the fallback if the user shrinks the window. + private void RightSizePanel() + { + RootGrid.Loaded += (_, _) => + { + RootGrid.Measure(new Windows.Foundation.Size(double.PositiveInfinity, double.PositiveInfinity)); + var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(this); + double scale = GetDpiForWindow(hwnd) / 96.0; + var id = Microsoft.UI.Win32Interop.GetWindowIdFromWindow(hwnd); + var appWindow = Microsoft.UI.Windowing.AppWindow.GetFromWindowId(id); + int w = (int)(RootGrid.DesiredSize.Width * scale); + int h = (int)(RootGrid.DesiredSize.Height * scale); + appWindow.Resize(new Windows.Graphics.SizeInt32(w, h)); + // ponytail: best-effort min-size lock if the presenter supports it; else ScrollViewer + // covers shrink. Guarded so a missing API in 1.8 doesn't break the build. + if (appWindow.Presenter is Microsoft.UI.Windowing.OverlappedPresenter p) + { + p.PreferredMinimumWidth = w; + p.PreferredMinimumHeight = h; + } + }; + } +``` + +> If Context7 confirms `PreferredMinimumWidth/Height` do **not** exist in 1.8, delete the `if (appWindow.Presenter ...)` block (size-to-content alone satisfies the requirement; ScrollViewer is the shrink fallback). Do not add a WM_GETMINMAXINFO subclass — out of scope. + +- [ ] **Step 2: Wire the custom title bar in the ctor** + +After `InitializeComponent();` (~73, before `RightSizePanel();`), extend into the title bar and set the drag region: + +```csharp + InitializeComponent(); + ExtendsContentIntoTitleBar = true; + SetTitleBar(AppTitleBar); + RightSizePanel(); +``` + +- [ ] **Step 3: Commit** + +```bash +git add src/CameraOnScreen.App/MainWindow.xaml.cs +git commit -m "feat(app): custom title bar + size-to-content window" +``` + +--- + +### Task 5: Regroup the panel XAML + +**Files:** +- Modify: `src/CameraOnScreen.App/MainWindow.xaml` + +**Interfaces:** +- Consumes: `Vm` without `Zoom` (the Zoom `Slider` is deleted); the static helpers `QualityEnabled`, `ExposureSliderEnabled` (unchanged); `StatusLine`, `MaxineAttribution`, `NotAvailableVisibility`, `EyeContactNotAvailableVisibility` (unchanged). +- Produces: a window with `x:Name="RootGrid"`, an `x:Name="AppTitleBar"` title bar row, two grouped cards, and a help `InfoBar`. + +> Not locally buildable. Verified at CI build + visual gate. Keep every binding path byte-identical to today's (only grouping/containers change) so no code-behind helper goes missing. + +- [ ] **Step 1: Rewrite `MainWindow.xaml`** + +Replace the whole file with the grouped layout. Title-bar row (row 0), scrollable body (row 1), pinned attribution (row 2): + +```xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +