docs(handoff): rewrite for pushed 0c55645 (ON-AIR armable signed-out Good Dog ruling + ONE PillRadio test)
This commit is contained in:
+45
-132
@@ -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 <dll> --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 "<files>"`'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.
|
||||
- `ai.md` §Local recording (TASK 18, ≈lines 605-640) = the authoritative pill/state-model doc
|
||||
(record-OR-live, armable signed-out, Start-button-face).
|
||||
|
||||
Reference in New Issue
Block a user