A webcam dragged between sources reverted to the bottom of the stack on every relaunch: Source and WebcamSceneConfig each carried independent per-type SortOrder counters, and Load appended all Sources before all configs. Save now stamps both tables' SortOrder from the element's index within scene.Elements; Load merges the two tables' rows by that shared z (sources-first tie-break preserves legacy rows). Cross-type reorder now survives a fresh LayoutStore reload. Task 37 queued: defaults vs current layout split (creator directive) — capture out-of-scope work in TASKS.md rather than folding it in.
9.5 KiB
HANDOFF — 2026-09-20 (cross-table z-order bug fixed: webcam between sources survives reload; 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 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 inApp.xaml.cs:32asBuild {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.Failedfires mid-stream andSafeStopAsyncdisposes reader+capture while the frame thread sits inTryAcquireLatestFrame. 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 locksytLive.exe(MSB3021).- Build/tests: Windows dotnet host (
/mnt/c/Program Files/dotnet/dotnet.exe). 0 warnings — only./scripts/verify.sh "<files>"'s clean build counts. RunningytLive.csprojalone does NOT rebuildytLive.Tests.dll— run the Tests csproj beforevstest. 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.dbthere 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-introduceStartWatchingwithout 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=1loads a healedBackgroundelement pinned atElements[0](created byEnsureBackgroundonStagedSceneset) — 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:\tmpoutis 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.