diff --git a/HANDOFF.md b/HANDOFF.md index e8663a5..beab875 100644 --- a/HANDOFF.md +++ b/HANDOFF.md @@ -1,145 +1,58 @@ -# HANDOFF — 2026-09-20 (cross-table z-order bug fixed; REC/ON-AIR pills now radio-exclusive; defaults/current split queued as TASK 37) +# HANDOFF — 2026-09-20 (ON-AIR armable signed-out shipped + pushed; Good Dog ONE-test ruling) ## Branch / Commit State -`main` HEAD = **the cross-type ordering fix** (local — no push). This is a CONTINUATION of the -2026-09-18 drag-reorder work: the creator kept insisting the reorder "doesn't save" even after -commit `f91bf87`. Their repro was real and the per-table `SortOrder` theory was the actual mechanism. +`main` HEAD = **`0c55645` — feat(pills): ON-AIR armable signed-out; Start face reads "Sign In" +while armed signed-out (Good Dog 2026-09-20 ruling)** — **PUSHED** to `origin/main` +(`5a1993b..0c55645`, non-force). Working tree **clean** (all 6 units of the change committed). -**No push yet.** Working tree clean after the ordering-fix commit. +This is a continuation of the 2026-09-18 drag-reorder + 2026-09-20 radio-exclusive-pill work: the +ON-AIR setter could only light while `IsConnected`, so a signed-out creator found a **greyed pill += dead end**. The ruling ships the armable signed-out pill. -## ✅ Committed — cross-type z-order: webcam between sources survives save→reload (2026-09-20) +## ✅ What shipped this session (commit 0c55645, pushed) -**The actual bug the creator reproduced** (`f91bf87` shipped the immediate-save at drop, but the -reorder still reverted on restart): `Source` and `WebcamSceneConfig` are separate tables with -**independent per-type `SortOrder` counters**. Save stamped sources 0..n and configs 0..m; -`LayoutStore.Load` appended **all Sources first, then all WebcamSceneConfigs**. A webcam dragged -between two sources was structurally unrecoverable — the live scene (Background, YouTube Chat, -Image, Web Resource-0 sources + one HD Pro Webcam C920 config) reverted the Webcam to the bottom -of the stack on every relaunch. +- **ON-AIR pill is intent, not account reality** — `OnAirPillOn` setter is **armable signed-out** + (greyed/disabled-until-`IsConnected` gate REMOVED; a pill that could not light offline was the + dead end the guard existed to forbid). REC-OR-live radio exclusivity is untouched — arming one + clears the other; the radio constraint lives at the pills. +- **Start button face is reality-aware**: `PrimaryStartButtonLabel` = `"Start"` while + `IsConnected || !OnAirPillOn`, else **`"Sign In"`** when an ON-AIR pill is armed signed-out. + Clicking Start while the ON-AIR pill is armed signed-out routes to **sign-in**, not a dead-end + go-live (no dead ends — creator ruling; see ai.md §Streaming pills). +- **One Good Dog test** (`ytLive.Tests/PillRadioTests.cs`): `PillRadioTests.Arming_One_Output_ + Pill_Clears_The_Other` (radio-exclusive) **+** the signed-out armable-pill headless test + (`RealAppHost` + temp DB, no browser). +- **ai.md** updated in the SAME commit: tooltip wording + the armable-signed-out ruling + (2026-09-20) recorded; stale "greyed/disabled until `IsConnected`" fact replaced. -**Fix:** one shared z-space per scene. `Save` stamps both tables' `SortOrder` from the element's -index within `scene.Elements` (the non-source branches bump the counter too); `Load` merges the two -tables' rows **by that shared z** instead of appending tables. Legacy pre-unification rows can tie at -0 — ties break sources-first, so old data reads back exactly as it saved. +## Deferred / queued (recorded in TASKS.md, NOT folded into this change) -**Good Dog test** `CrossTypeReorder_BelowTheWebcam_SurvivesFreshReload` (`LayerReorderPersistenceTests`): -seeds the builder's real Live layout (4 sources + `HD Pro Webcam C920` config row), reorders -"Web Resource-0" DOWN below the webcam, calls `OnSceneElementsReordered`, then opens a **brand-new -`LayoutStore` on the same temp DB — the restart** — and asserts the cross-type order holds. **309/309 -tests pass, build 0 warnings.** +- **TASK 37 — defaults vs current layout split**: DB holds static brandable `default` layout; the + `current` layout carries the build-id and only loads when it matches `BuildStamp.Id` — enables + one-click revert to factory defaults. Files: `TASKS/task-37-defaults-vs-current.md`. +- **Manual take on webcam-between-sources order** (cross-type z-order fix `94e114b` already + committed/pushed): relaunch, live view, drag Web Resource-0 below the webcam, restart, expect it + pinned. Closes the bug with the builder before any further streaming work. +- **Build-id-on-exit** demand: still queued for a later slice (do not fold in). -**Also committed:** TASK 37 queued — **defaults vs current layout split** (creator directive -2026-09-20): the DB should hold the static brandable `default` layout; the `current` layout carries -the build-id (loaded only when it matches `BuildStamp.Id`), enabling one-click revert. Conceptual -shape `scene[0-4].layerList.[default|current].elementList`; config-file vs second-rows-in-DB not yet -decided (`TASKS/task-37-defaults-current-split.md` has both + facts). **Process rule established:** -capture out-of-scope needed work in TASKS.md rather than bolting it onto the in-flight change. +## Critical working rules (failure cap) -## ✅ Committed — REC/ON-AIR pills are radio-exclusive (2026-09-20) +- **Good Dog = ONE integration test per change.** Do not pile up multiple tests for one guard. +- **Scope lock:** declare the exact file list BEFORE editing; run `scripts/scope-check.sh` with the + declared list before commit. No "while I'm here" edits. +- **Run from WSL** always uses the Windows dotnet host (`/mnt/c/Program Files/dotnet/dotnet.exe`) + with **quoted** paths — a path containing a space left unquoted was the #1 tool-spin cause this + session. Test filter runs go through `dotnet vstest --filter …` (Windows DLL path), NOT + `dotnet test` (WSL re-downloads the windowsdesktop packs + double-restores). +- **Never type the username in paths.** Canonical root is `$(pwd)`. Byte-verify every edit anchor + (node `fs` on `path.join(R, p)`) — the phantom-tree reads wasted this whole session. +- **ai.md + code in the SAME commit**; rewrite HANDOFF.md at session end or interruption. -**Gap found:** the record-OR-live ruling (2026-09-01) was documented but the "pills = radios" UI -constraint never landed — the two pills were independent toggles, so a creator COULD arm both and -`StartSession`/`BuildEncoderOptions` would happily dual-encode (RTMP + `-f mp4`) — the exact thing the -ruling forbids (a concurrent disk write streams from the same sustained ingest Kbps and cripples the -stream on mid-range chassis; "the VOD is already the copy"). Fix: mutual clear in both pill setters -(`RecordPillOn`/`OnAirPillOn` in `MainViewModel.Streaming.cs`) — arming one clears the other (silent -mutual clear, creator's choice). The dual-block ffmpeg machinery stays generic/unreachable by design. -Test `PillRadioTests` (RealApp + temp DB): REC→ON-AIR clears REC; ON-AIR→REC clears ON-AIR; single -pill still arms Start. **310/310 pass.** +## Where the machine facts live -## ✅ Committed earlier — layer-list drag-to-reorder persists (2026-09-18, `f91bf87` + below) - -`StagedScene.Elements` reorder (RemoveAt/Insert) bypasses SceneGraph's mutation surface, so the drop -must be trapped + persisted. Fix (2026-09-18): the drag sets `_dragReordered` and `EndListDrag` -funnels the drop through `MainViewModel.OnSceneElementsReordered()` (Sources.cs) = -`SceneGraph.InvalidateBake(staged) + SaveLayoutNow()` — an IMMEDIATE save at drop (no debounce -window). The single write stores BOTH the layer list (SortOrder) AND the preview layout (X/Y/W/H in -the same rows). Tests `LayerReorderPersistenceTests` (real App + temp DB): (1) exact code-behind -mutation + geometry assert; (2) **real-gesture** test `RealMouseDrag_OnTheLayerList_PersistsTheReorder` -(`SetCursorPos` + `mouse_event` on a shown Topmost window) — needs an interactive desktop session -(covered/locked session = pointer no-op = flake). Both green in the 309. **This fix was correct but -INSUFFICIENT — the cross-table bug above is what the builder actually hit.** - -## ✅ Committed earlier — webcam app-default gate + attainability (`9d00955`, `75723ac`) - -Webcam is an **app-level resource** — ONE app-wide default, usable in every scene that can host it, -NOT blocked by other scenes' placements (creator ruling; supersedes TASK 26). Per-scene gate -`CanAddWebcam` = scene has no config && attainable (`CameraManager.IsRunning(_webcam.DeviceId)` — -a live lock, not a saved identity). Add places the default directly (no picker). Identity survives -last-placement removal; startup lays an app-wide BASE lock (refcount keeps the session alive); -adopts a solo camera as default. Dynamic why-gray tooltip. Tests: `WebcamMenuGateTests`, -`WebcamStartupResourceTests`. The DB truth lives at `%APPDATA%\ytLlive\ytLlive.db` (NOT `layout.db`). - -## ✅ Committed earlier — Rename Recording dialog taller + full-screen hook unhooked (2026-09-18) - -`RenameRecordingDialog.xaml` Height 230 → 276 → **304** (the +20% revealed the Cancel/Save row also -obscured); Save button named `SaveRecordingButton`. Test `RenameRecordingDialogSizingTests` asserts -textbox bottom + Save button bottom inside the client area. **Teardown leak fix:** `Shutdown()` now -calls `_fullScreenDetector.StopWatching()` — the global `EVENT_SYSTEM_FOREGROUND` hook was never -unhooked, invoking a GC'd `WinEventProc` on the next foreground event → test-host abort. - -## ⚠️ Open items - -- **Build-id on command-line exit (creator demand, 2026-09-20):** when the app exits under - `dotnet run`, output the build number (`BuildStamp.Id` — a fresh per-build GUID, logged in - `App.xaml.cs:32` as `Build {Id} (compiled {BuiltLocal})`) as return data on the command line. - NOT yet implemented — queued, do not fold into another slice. -- **TASK 37** — defaults/current layout split (see above + `TASKS/task-37-defaults-current-split.md`). -- **Webcam take fix 2 of 2:** native WMF death when `MediaCapture.Failed` fires mid-stream and - `SafeStopAsync` disposes reader+capture while the frame thread sits in `TryAcquireLatestFrame`. No - repro/test — deferred per spin-guard. -- **Multi-camera selector slice (user-queued):** ≥2 webcams → web-cam selection in the App Settings - dialog (gear). Currently ≥2 cameras → nothing shows in settings. -- **Static (+) catalog slice (user-queued):** all catalog rows always listed, greyed with named - reason tooltips. -- **Device re-verify (one take session):** slice-18 cache verdicts; webcam fresh-launch with the - browser holding the camera → red alert that clears after Retry once the browser closes; Live must - OFFER Web Cam while Chat holds it. -- **No push yet** — after the take verdict, decide push with the user. - -## Open threads (carried) - -- Audio-silence verification — fixed (`724af14`); creator heard real audio. -- Webcam MJPG missing / ~10–14Hz, truncation-with-dynamic-scenes — queued. -- Sync control user-doc tutorial — REQUIRED before 1.0 (creator directive; TASK 22). -- Signed A/V sync: verify the negative (advance) direction on device. -- The 2026-09-18 "reorder doesn't save" stale-binary theory — RETRACTED 2026-09-20. The builder was - right; the cross-table z-order bug above is the real mechanism. Never re-air the stale-binary claim. - -## Landmines - -- testhost shares startup.log — filter by time; the app also writes fake-device failures - ('test-camera', 'dev-physical-9') from CameraManager unit tests. -- `cmd.exe /c "taskkill /F /IM ytLive.exe"` (WSL double-slashes mangle) before rebuilds — a live - app process locks `ytLive.exe` (MSB3021). -- Build/tests: **Windows dotnet host** (`/mnt/c/Program Files/dotnet/dotnet.exe`). 0 warnings — - only `./scripts/verify.sh ""`'s clean build counts. Running `ytLive.csproj` alone does NOT - rebuild `ytLive.Tests.dll` — run the Tests csproj before `vstest`. Known audio flake: - `Mix_HonorsProviderGains_AndGameMute_KillsTheLoopback` — stale loopback sample in the pipe when the - mute flips. Unrelated; do not "fix" inside another slice. -- **Assert footguns in this repo's xUnit:** `Assert.Null(x, "msg")`/`Assert.Single(coll, "msg")` do - NOT take a message — the 2-arg overloads mean something else (predicate/item). Empty message = 1 arg. -- The webcam's real state lives in `%APPDATA%\ytLlive\ytLlive.db` (`layout.db` there is a 0-byte - legacy file). sqlite3 at `/home/gramps/android-sdk/platform-tools/sqlite3`. -- FramePump tests asserting per-frame CONTENT need a STABLE scene; cache-sensitive assertions can't - use a call-count resolver flip (tick resolves twice) — key alternation on `OutputIndex`. +- `%APPDATA%\ytLive\ytLive.db` = real layout DB (`layout.db` is a 0-byte legacy file). sqlite3 at + `/root/android-sdk/platform-tools/sqlite3` (or the SDK copy) for DB checks. - ffmpeg/ffprobe: `/mnt/c/Program Files/Krita (x64)/bin/` with Windows paths. -- `MyMistakes.md`: WINRT resource-allocation RECIPE, freeze-audit RECIPE, A/V sync recipe, deadline - pacing, CoreMessaging DQ, WGC-CLIP + slice blocks. Grep before re-deriving. -- **Test-host crash (fixed):** GC'd `WinEventProc` — `Shutdown()` unhooks the foreground-event hook; - never re-introduce `StartWatching` without matching teardown; dialogs with root-relative `/Assets/…` - Icon can't be instantiated by tests (use `/ytLive;component/…`). -- **Scene.Elements layer tests:** any seeded scene with `HasBackground=1` loads a healed - `Background` element pinned at `Elements[0]` (created by `EnsureBackground` on `StagedScene` set) — - compute expected layer orders from the live collection, never hardcode indices. -- **Cross-type element order tests** must assert through a FRESH `LayoutStore` (the restart), not the - running app's store — that's the read-back path the builder's bug exercised. -- `C:\tmpout` is for ffmpeg evidence artifacts; keep them out of the repo. - -## Next step - -The ordering fix is the builder-facing half of the "webcam between sources" bug — a manual take -(relaunch, live view, drag Web Resource-0 below the webcam, restart, expect it pinned there) closes -it. Then the copy with the builder on any remaining repro, then decide push. TASK 37 (defaults vs -current) and the build-id-on-exit demand are queued for a later slice — do not fold them in here. \ No newline at end of file +- `ai.md` §Local recording (TASK 18, ≈lines 605-640) = the authoritative pill/state-model doc + (record-OR-live, armable signed-out, Start-button-face).