docs(plan): TASK 40 App Settings round — saved plan (units A-D, one Good Dog test each)

This commit is contained in:
2026-09-22 08:37:05 -07:00
parent 197ee815a0
commit 4d9188ab2e
3 changed files with 140 additions and 4 deletions
+11 -3
View File
@@ -55,6 +55,7 @@
| 37 | Defaults vs current layout split | ☐ Queued (2026-09-20) | [`TASKS/task-37-defaults-current-split.md`](TASKS/task-37-defaults-current-split.md) |
| 38 | Capture Window… backdrop (in-app window picker) | ✅ Done — shipped 2026-09-21 | [`TASKS/task-38-window-backdrop.md`](TASKS/task-38-window-backdrop.md) |
| 39 | YPP journey tracker (slice 1: current-scope data) | 🔶 Slice 1 ✅ — 2026-09-22; slice 2 (Analytics + ETA) queued | [`TASKS/task-39-ypp-journey-tracker.md`](TASKS/task-39-ypp-journey-tracker.md) |
| 40 | App Settings round: active-camera picker + config dialog, gear move, defaults, accent | ☐ Queued (2026-09-22) — plan saved | [`TASKS/task-40-app-settings-round.md`](TASKS/task-40-app-settings-round.md) |
---
@@ -73,9 +74,16 @@
slice shipped same day — webcam = app-level default, one per stream per scene, Add places the
default directly, offered only when a camera is ATTAINABLE (identity + live session, not just a
saved identity), app base lock keeps the session after the last placement is removed, startup
adopts a solo camera as default. TASK 26's app-wide gate superseded by creator directive. Next
slices (user-queued): multi-camera webcam selector in the App Settings dialog (gear), static (+)
catalog rows (Background, YouTubeEvent, quoted labels) with reason-greying.
adopts a solo camera as default. TASK 26's app-wide gate superseded by creator directive. The App
Settings webcam selector is now **TASK 40 Unit A** (plan saved 2026-09-22). Remaining next slice
(user-queued): static (+) catalog rows (Background, YouTubeEvent, quoted labels) with reason-greying.
- **TASK 40 — App Settings round** — plan saved 2026-09-22 (`TASKS/task-40-app-settings-round.md`),
four units, one Good Dog test each: **A** active web camera section under Recordings (Change…
sub-menu = live enumeration incl. vcams; Tune… config dialog via `ICameraControlProbe`, live-only
tweaks), **B** gear moved top-left right of the wordmark (left-click App Settings, right-click
Bug/Feature/About), **C** persist output resolution default + surface default scene
transition/duration + confirm-before-End, **D** accent/theme color (consolidate 69 hardcoded
accent sites into a DynamicResource brush + picker).
- **TASK 37** — queued (2026-09-20): split the layout data into a `default` set (the established DB)
and a `current` set carrying the build-id; a saved `current` is honored only by the same build-id,
otherwise defaults load — enables one-click revert to defaults too. Capture needed work OUT of
+127
View File
@@ -0,0 +1,127 @@
# 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
+2 -1
View File
@@ -453,7 +453,8 @@ This replaces the old five-seeder cluster (`Seed{Starting,Brb,Ending,Chat}Backgr
`CameraManager.AcquireAsync(device)` as an app-wide lock — on failure a **persistent red alert**
(`WebcamLockAlert`, bottom of the Layers panel with a Retry button) re-polls every 5 s
(`_webcamLockPollTimer`) until a lock succeeds, and any first real frame also clears it; **≥2** →
deliberately no auto-lock — selection belongs to the App Settings dialog (gear) **next slice**.
deliberately no auto-lock — selection belongs to the App Settings dialog (gear) **queued — TASK 40
Unit A (App Settings round, plan saved 2026-09-22)**.
The single-camera branch **always acquires** (it no longer skips when configs already hold the
session): that acquire is the app's own BASE ref, so removing every scene's placement leaves
`RefCount = 1` and the session alive — the gate stays satisfied and the Web Cam row stays offered,