Files
LlamaCasty/TASKS/task-40-app-settings-round.md
gramps e69db4d26c feat(ui): TASK 42 top bar redesign — one Record-or-Stream surface (2026-09-22)
The bar now renders ONE surface from the FIRST decision the creator makes —
Record or Stream — with each world being the whole bar. Creator directive:
"I wrote the fucking thing and I still can't figure-out how to do stuff",
"forget this one dog plan bullshit". OBS/streaming-tool reference for the
one-surface paradigm: https://obsproject.com (single mode toggle + context
actions) — cited per spin-guard habit; the go-live/test pattern follows
CEV (https://cev-desktop.aSean.xyz) precedent.

- Mode: single segmented REC|ON-AIR switch (SegmentToggle/SegmentLabel in
  Themes/Controls.xaml); radio-exclusive world selection, one tap to flip.
- Record world: switch + Start Recording, zero YouTube identity.
- Stream world: switch + Go Live + Test + account zone right (Sign In until
  connected, then avatar with Change Account/Logout context menu).
- Test is a child of Stream: procs only stream-armed AND signed-in.
- Running: one reality line "dot word elapsed" (REC/LIVE/TEST; green/red/
  gold) + End; worlds + switch retire.
- Gear moved up from bottom bar, ~3 wordmark letters past the brand, single
  click opens Settings/Bug/Feature/About menu (TASK 40 Unit B shipped early).
- Fix folded in: BeginTestStream now arms OnAirPillOn explicitly (unarmed
  pump booted with zero encoder outputs -> "At least one output is required"
  forced-stop cascade from TASK 41's pipeline).
- VM: world/reality props + RaiseTopBarModes() wired into StreamStatus,
  pill, IsRecording, IsTestStream, IsConnected setters.

Doc: ai.md top-bar model, ViewModels/index.md, Controls/index.md, TASKS.md,
HANDOFF.md, new TASKS/task-42-top-bar-redesign.md; task-40 note.

Tests: TopBarModeTests.cs (new Good Dog), PillRadioTests + TestStreamTests
gates updated. Build 0 warnings; full suite 314/315 — single abort is the
pre-existing env-dependent RealMouseDrag test (Path of Exile 2 running)
2026-09-22 17:57:06 -07:00

136 lines
8.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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)
> ✅ **SHIPPED 2026-09-22** as part of TASK 42 (top bar redesign) — gear is top-right
> of the brand on `Controls/TopBar.xaml`, single click opens the Settings/Bug/Feature/
> About menu (`GearButton_Click` moved from BottomBar). See
> [`task-42-top-bar-redesign.md`](task-42-top-bar-redesign.md).
- `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.
> **Left as follow-ups** (not part of the shipped gear): the overlay scrim row-span bonus (still
> open) — the new gear sits in `OverlayHost`'s Row 0 but the scrim rows are not yet spanned over
> it. Revisit when rows are next touched.
## 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