docs(plan): TASK 40 App Settings round — saved plan (units A-D, one Good Dog test each)
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user