# HANDOFF — 2026-09-20 (cross-table z-order bug fixed; REC/ON-AIR pills now radio-exclusive; defaults/current split queued as TASK 37) ## 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. **No push yet.** Working tree clean after the ordering-fix commit. ## ✅ Committed — cross-type z-order: webcam between sources survives save→reload (2026-09-20) **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. **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. **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.** **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. ## ✅ Committed — REC/ON-AIR pills are radio-exclusive (2026-09-20) **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.** ## ✅ 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`. - 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.