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

8.8 KiB
Raw Permalink Blame History

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 MediaDeviceControls — 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.

  • 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 (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