Commit Graph

4 Commits

Author SHA1 Message Date
gramps 9d00955004 feat(webcam): app-default gate slice — per-scene offer, Add places default directly, identity survives removal
Chat's camera no longer greys Web Cam in Live (per-scene max, not app-wide);
TASK 26 superseded by creator directive (app-level resource model). Add Webcam
now places the existing app default without the picker; the picker runs only
for the initial selection. Removing the last placement keeps the identity
(_webcam never nulled) so Add stays offered. Startup pass adopts a solo camera
as the app default, so a clean layout offers the layer in Live/Chat at once.
Dynamic WebcamAddToolTip names the why (incl. the 'graduated to OBS' line).

Good Dog: WebcamMenuGateTests rewritten (real app + temp DB + camera seams) —
identity in Chat does not gray Live; Add in Live places same wc-1 no picker;
scene-with-placement stays gray; identity survives both removals (DB row 1).
WebcamStartupResourceTests single-lock fact asserts CanAddWebcam after adoption.
Docs same commit (ai.md supersession, TASKS.md, HANDOFF.md). 304/304, 0 warnings.
2026-09-17 08:56:04 -07:00
gramps 1e4017df03 feat(webcam): resource lifecycle startup slice — poll-on-start, single-cam lock, persistent Layers alert
The creator couldn't add a webcam to Live (grayed app-wide) and nothing in the
app explained why. Ground truth from the live DB: one Webcam identity AND one
WebcamSceneConfig in the Chat scene — so the gray was the single-identity rule
working, but the reason was unobservable. This slice makes the webcam a
resource the app validates and locks, mirroring how OBS reserves its devices.

At startup we enumerate the OS once (ValidateWebcamResourceStartupAsync, fired
after LoadLayout, stored as WebcamStartupValidationTask for tests to await):
- 0 webcams -> app runs on, layer inactive, no alarm
- exactly 1 -> attempt CameraManager.AcquireAsync as an app-wide lock; on
  failure show a persistent red alert at the bottom of the Layers panel
  (WebcamLockAlert + Retry) that re-polls every 5s and clears itself the
  moment the camera locks, or on any first real frame
- >=2 -> deliberately no auto-lock; camera selection belongs to the App
  Settings dialog (gear) — next slice

CameraManager.IsRunning(deviceId) tells the pass a session already exists
(started OR still starting) so loaded identity configs count as the lock and
the pass never double-acquires. Test seams mirror LayoutPathOverride:
CameraEnumeratorOverride / CameraFrameSourceFactoryOverride so the startup
probe never touches real hardware under test.

Good Dog test: WebcamStartupResourceTests x3 — single-cam locked + app runs on,
zero-cams no-alarm/no-lock, lock-fails -> red alert -> Retry -> clears. Full
suite 304/304, build 0 warnings, scope-check passed.

Docs in-commit: TASKS Open items + ai.md Webcam section + HANDOFF rewrite.
Also corrects the record: 'NVIDIA Broadcast opens the webcam exclusively' was a
suspect-list claim (CameraConflictProbe reads process names only, no handles)
— not restated as fact.
2026-09-17 08:03:58 -07:00
gramps 670fe3a10a TASK 31: SceneGraph component + baked-crust compositor optimization
- Add ElementKind (Static/Dynamic) to SceneElement base; Source/WebcamSceneConfig classify
- Services/SceneGraph.cs: owns the Scenes collection (ViewModel's Scenes delegates to it),
  the element mutation surface (Add/Insert/Remove/Move, each invalidating the bake), and the
  queries that were scattered LINQ (GetBackground/GetWebcam/GetChatBoxes/GetSplitPoint/IsStatic)
- SceneCompositor: split-aware BakeStaticBase + CompositeLayers + Render(.., staticBase, split);
  builds/caches the static base below the split point in source-rect space
- FramePump: optional SceneGraph -> optimized bake+composite path; falls back to full render
- MainViewModel: routes element mutations through the graph; invalidates the bake on static
  layout/opacity/visibility/useDefaultBackground changes and after background heal/Ensure
- Integration test SceneGraphTests.BakedStaticBase_WithDynamicLayer_CompositesCorrectly

Derivative survey (mandated): tried before writing — OBS does per-source opacity/visibility
caching and static-scene baking; this mirrors OBS's 'cached static source' optimization.
3 documented defensive deviations from the spec (ChatOverlayLayer stays decoupled;
background helpers stay VM-static for direct testability; full facade peel deferred post-1.0)
in TASKS.md + ai.md. Scope check passed; clean build 0 warnings.
2026-08-31 17:53:34 -07:00
gramps e2867198b9 refactor: split Webcam out of MainViewModel — Commit 5/12 (tag refactor-commit-5) 2026-08-30 07:35:21 -07:00