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)
8.8 KiB
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)
- 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.
- The settings gear (bottom bar, lower-left, icon-only, context-menu) "looks lost" — re-locate and/or label it.
- "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/#22c55esites across 15 files + VM brush literals + the persisted per-sourceSource.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 viaCameraPickerDialogon 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 bindsSelectedTransitionType/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 handlerBottomBar.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.xamlowner/geometry/theme): probes the device'sVideoDeviceControllerMediaDeviceControls — Brightness, Contrast, Hue, Saturation, Sharpness, Gamma, WhiteBalance, BacklightCompensation, Exposure, Focus, Pan, Tilt, Zoom — showing only the ones the device reports. Each row: Auto checkbox (whenCapabilities.AutoSupported) + Slider Min/Max/Step applying live viaTrySetValue/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.SharedReadOnlyinit (the MJPG stream-property negotiation already works there, but extended controls are a separate path). Mitigation ladder: (1) probe-captureSharedReadOnly→ (2) the running frame source's ownVideoDeviceController→ (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_Clickmoved from BottomBar). Seetask-42-top-bar-redesign.md.
Controls/TopBar.xaml:IconButtongear right of the brand Button, gap ≈ 3 wordmark letters; left-clickOpenSettingsCommand; ContextMenu keeps Report Bug / Feature Request / About. Remove the gear (+ its click handler) fromControls/BottomBar.xaml(.cs).- Bonus: TopBar sits inside the overlay scrim span (
OverlayHost Grid.RowSpan=3covers 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 keyOutput.Resolution), restore at startup. - Default scene transition + duration — surface the persisted-but-uneditable
TransitionType/TransitionDurationMsas controls in App Settings. - Confirm before End Stream — toggle; when on,
EndStreamCommandconfirms 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.xamlaccent brush, consumed viaDynamicResourceacross 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.mdgains 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(new — this file)TASKS.md— row 40 + webcam "next slices" bullet (selector → TASK 40) + TASK 40 open itemai.md— ≥2-camera bullet points at TASK 40 Unit AHANDOFF.md— in-flight plan + next step