# TASK 40 — App Settings round: active camera + config dialog, gear move, defaults, accent **Status:** ☐ Queued — **plan saved 2026-09-22** (creator: "save this plan to the memory files"; implementation to start on the next task spec). Sub-units A → B → C → D, each a separate scope-checked work unit with its own Good Dog integration test. ## Creator asks (2026-09-22, three) 1. **Settings → App Settings:** add a new section **under "Default Location for Recordings"** for the *active web camera* — (a) triggering it queries Windows for the **current** list of webcam resources **including vcams**, presented as a **sub-menu list**; (b) selecting a camera opens a **dialog with the available configuration params for tweaking**. 2. The settings **gear** (bottom bar, lower-left, icon-only, context-menu) "looks lost" — re-locate and/or label it. 3. "Surely there's more tweakables?" — add other settings. ## Locked decisions (user answers, 2026-09-22) - **Three units A → B → C** (recommended split honored; one Good Dog test + one commit each). - Camera tweaks are **live-only** — no persistence, no schema change in Unit A. A per-camera "re-apply on preview start" preset stays a follow-up if ever wanted. - Gear: **top-left, to the right of the "LlamaCasty" wordmark, gap ≈ 3 wordmark letters**, icon-only; **left-click = App Settings** (direct), **right-click = Report Bug / Feature Request / About** menu (relocated with the button off BottomBar). - Unit C picks: **default output resolution**, **default scene transition + duration**, **Confirm before End Stream**. - **Accent/theme color** was picked too, but it is heavy (69 hardcoded `#e94560`/`#ef4444`/`#22c55e` sites across 15 files + VM brush literals + the _persisted_ per-source `Source.ChatMemberColor`) → split out as **Unit D** with its own scope + test. ## Facts this plan sits on (all verified in code, 2026-09-22) - Webcam identity is **app-level**: `LayoutStore.Webcam` (Webcam table), picked via `CameraPickerDialog` on first Add, swapped via "Change Webcam…" context menu (`MainViewModel.Webcam.cs` → `ChangeWebcamAsync` / `SwapWebcamIdentityAsync`: release old, re-acquire per scene config, persist). - Startup resource check auto-locks only when **exactly one** camera is found; **≥2 cameras → deliberately no auto-lock, selection left to the App Settings dialog (gear)** (ai.md §Startup resource lifecycle) — this plan's Unit A IS that selector. - Enumeration is already "current list incl. vcams": `MediaCaptureCameraEnumerator` → `DeviceInformation.FindAllAsync(DeviceClass.VideoCapture)` (same source OBS consumes; caveat: a vcam must be a *registered capture device* to appear). - **Default scene transition type + duration are persisted (`TransitionType` / `TransitionDurationMs`) and loaded every start, but editable NOWHERE in the UI** (nothing in XAML binds `SelectedTransitionType`/`TransitionDurationMs`) — Unit C surfaces an existing knob. - **Output resolution resets every launch** (`_selectedQuality = QualityOptions[0]` in the MainViewModel ctor; bottom-bar ComboBox not persisted). - Gear today: `Controls/BottomBar.xaml:13` (far-left column 0), icon-only, ContextMenu (App Settings / Report Bug / Feature Request / About); click handler `BottomBar.xaml.cs`. ## Unit A — App Settings "Active Web Camera" (your ask 1) New group **under** Recordings in the App Settings overlay (`OverlayHost.xaml:39` settings StackPanel): current camera display + **Change…** + **Tune…**. - **Change…** → **fresh enumeration per trigger** → ContextMenu **sub-menu** listing cameras (current = checkmarked; disabled "No cameras found" item when empty). Selection: `_webcam == null` → adopt + base-lock (mirrors the single-camera startup path); else → `SwapWebcamIdentityAsync` (release old / re-acquire per scene / Webcam row persisted). - **Tune…** (enabled when a camera is active) → **CameraConfigDialog** (new window; mirrors `CameraPickerDialog.xaml` owner/geometry/theme): probes the device's `VideoDeviceController` **MediaDeviceControl**s — Brightness, Contrast, Hue, Saturation, Sharpness, Gamma, WhiteBalance, BacklightCompensation, Exposure, Focus, Pan, Tilt, Zoom — showing only the ones the device reports. Each row: **Auto checkbox** (when `Capabilities.AutoSupported`) + **Slider Min/Max/Step** applying live via `TrySetValue`/`TrySetAuto`; empty state "No adjustable controls on this camera" (common on cheap cams and vcams). - **⚠ Tech risk, verified during implementation (not assumed):** whether MediaDeviceControl writes succeed under our `MediaCaptureSharingMode.SharedReadOnly` init (the MJPG stream-property negotiation already works there, but extended controls are a separate path). Mitigation ladder: (1) probe-capture `SharedReadOnly` → (2) the running frame source's own `VideoDeviceController` → (3) read-only controls + an "applies to a stopped preview" note. **Spin-guard:** a second failed attempt = research the established answer (OBS does this via DirectShow VideoProcAmp) before writing a third. - **Test seam:** `Services/CameraControlProbe.cs` — `ICameraControlProbe` (capability/value/ auto read + `TrySet*`) with a MediaCapture-backed implementation; the Good Dog test drives a fake that records calls. (The WinRT/hardware probe itself is not unit-testable.) | Unit A — declared files | | |---|---| | touch | `Controls/OverlayHost.xaml`, `ViewModels/MainViewModel.Webcam.cs`, `ViewModels/MainViewModel.cs` | | new | `Services/CameraControlProbe.cs`, `CameraConfigDialog.xaml(+.cs)`, `ViewModels/CameraConfigViewModel.cs`, `ytLive.Tests/AppSettingsWebcamTests.cs` | | docs | `TASKS.md`, `ai.md`, `TASKS/task-40-app-settings-round.md`, `HANDOFF.md` | **Good Dog test `AppSettingsWebcamTests`** (RealApp + temp DB, reuses existing `CameraEnumeratorOverride` / `CameraFrameSourceFactoryOverride` seams): (1) settings panel → Change… lists the fake cameras (incl. vcam-styled names); (2) pick B when A is current → identity swapped, A released, B acquired, Webcam row persisted, B checkmarked; (3) pick when `_webcam == null` → adopt + base-lock acquired; (4) fake `ICameraControlProbe` drives `CameraConfigViewModel` — Auto + slider changes forward to the probe. ## Unit B — gear to top-left (your ask 2) - `Controls/TopBar.xaml`: `IconButton` gear **right of the brand Button**, gap ≈ 3 wordmark letters; left-click `OpenSettingsCommand`; ContextMenu keeps Report Bug / Feature Request / About. Remove the gear (+ its click handler) from `Controls/BottomBar.xaml(.cs)`. - Bonus: TopBar sits inside the overlay scrim span (`OverlayHost Grid.RowSpan=3` covers rows 0–2), so the gear is correctly covered while an overlay is open — today it sits below the scrim. ## Unit C — the small defaults (your ask 3, rest) - **Default output resolution** — persist `SelectedQuality` (e.g. Settings key `Output.Resolution`), restore at startup. - **Default scene transition + duration** — surface the persisted-but-uneditable `TransitionType`/`TransitionDurationMs` as controls in App Settings. - **Confirm before End Stream** — toggle; when on, `EndStreamCommand` confirms before stopping live/recording. - One Good Dog test for the unit + docs in the same commit. ## Unit D — accent / theme color (own unit; not merged into C) - Consolidate the hardcoded accent literals into a `Themes/Controls.xaml` accent brush, consumed via `DynamicResource` across the ~15 files (brand "ty" run, YtButton primary, selected rings, live toolbar/glow, meters, trax, etc.). `Source.ChatMemberColor` (persisted per-source) stays per-source. - Picker + persistence via a Settings key (`App.AccentColor`). - Own Good Dog test (accent propagates) + docs (`ai.md` gains the hardcoded-accent → resource-brush consolidation note). - Caveat for the plan file: this is the widest diff of the round; the scope-check file list will be long by necessity and every touch is a genuine dependency of the consolidation. ## Memory updates (this plan's save, 2026-09-22) - [`TASKS/task-40-app-settings-round.md`](task-40-app-settings-round.md) (new — this file) - `TASKS.md` — row 40 + webcam "next slices" bullet (selector → TASK 40) + TASK 40 open item - `ai.md` — ≥2-camera bullet points at TASK 40 Unit A - `HANDOFF.md` — in-flight plan + next step