# HANDOFF — 2026-09-26 (TASK 47 shipping crash fixed; 2 local commits, NOT pushed) ## Branch / Commit State `main` — two local commits ahead of `origin/main` (= `2c9af94`... actually `origin/main` = `ecb329e`), **NOT pushed** (awaiting the creator's go / milestone signal): 1. `a11b15e` — **TASK 47** alert box video (built-in/custom clip + read-time fade + message ticker + unity audio) + `AlertLayerVideoTests` (fake decoder, 0 warnings, 318/319 with the known env flake → final full run 319/319 green). 2. *(uncommitted next)* — **the 2026-09-26 live-crash fix** (see below), scope = 3 files + docs: `Services/AlertOverlayLayer.cs`, `ytLive.Tests/AlertLayerVideoTests.cs`, plus docs (`TASKS/task-47-alert-videos.md` addendum, `MyMistakes.md` lesson, this file). Full suite **320/320** green, alert tests 3/3, `scope-check.sh` passed. ## ⚠️ The live crash + fix (this unit) The creator ran the shipped build in a test session: **test-tab alerts DID fire** (button commands run on the UI thread — startup.log `11:21:53` shows the sims and alert-clip audio in the live mix: `micLevel=0.000 loopLevel=0.000` yet `peakMix 0.375 → 0.733 → 0.891`, which can only be the alert ring; the default clip is materialized at `%TEMP%\ytLive-alert-941785b0-….mp4`, the video pipe verified against the pinned ffmpeg), but the app **crashed at `11:22:01.301`** — the first REAL message that round-tripped through the chat poller: System.ArgumentException: Must create DependencySource on same Thread as the DependencyObject. at ...MS.Internal.Data.DataBindEngine.ProcessCrossThreadRequests() Root cause: `AlertOverlayLayer.OnMessageReceived` had NO UI-thread marshal while `ChatOverlayLayer.OnMessageReceived` has one. A polled message ran `RefreshAlertPreviews` → `UpdatePreview` on the MTA poller thread and stamped `alertBox.VideoImageSource` (INPC-raised + WPF-bound) with a WriteableBitmap created there → binding engine cross-thread re-bind → crash. Fix: marshal the whole ingest via `Application.Current.Dispatcher.Invoke` (null-guarded so pure seams still run inline). Good Dog regression test `AlertLayerVideoTests.OnMessageReceived_FromPollerThread_MarshalsPreviewWritesToTheUiThread` (calls the seam from a raw MTA `Thread`, returns to the message loop, asserts on the UI thread that `VideoImageSource.Dispatcher` is the App's dispatcher — **red pre-fix, green post-fix, verified both ways**). Rule written into `MyMistakes.md`: every INPC-raising seam fed by the chat poller must marshal WPF-object writes to the UI thread. ## Follow-ups queued (NOT done in this unit) - **TASK 3 item 20 persistence half** — canonical `RewardEvents` SQLite table + `superChatEvents.list` (30-day) backfill + session-report rollup (still in-memory only). - TASK 3 item 16 (Text source); TASK 40 units A/C/D; TASK 32–36; TASK 12 master limiter — all queued unchanged. - If the creator still reports "nothing in the box" after this fix (their Live scene's "Stream Alerts" AlertBox is at 612,45 680×200; there is also a WebSource "Web Resource-0" bottom-right — confirm which element they're watching), investigate the video-render path live — but note all evidence (audio in the mix, materialized clip, verified pipe) says the native box played. ## Around the task (carried facts) - RealMouseDrag tests no-op while a game/fullscreen window steals the mouse (POE 2 seen 2026-09-22) — **close fullscreen windows before full-suite runs**; passes in isolation. - Test-env trap: saved OAuth session loads synchronously → force `vm.IsConnected` in signed-in tests. - `subscriberCount` YPP slice 2 needs re-consent — do not merge with other units. `$99/yr` Polar must become one-time before launch. `MARCOM.md`/`MONETIZATION.md` gitignored — never commit. - **API facts (TASK 44 + 45):** liveChatId unavailable before the broadcast is live (poll until active); `400 MISSING_REQUIRED_FIELD` from `liveChat/messages.insert` = wrong body shape (missing `snippet.type`), NOT auth/scope. - **TASK 47 sticky facts:** bgra from ffmpeg is opaque (alpha=255); audio pipe = carry-buffer loop; default clip = `%TEMP%\ytLive-alert-{id}.mp4`; decoder per-play, never the refcounted `MediaVideoSourceManager`. DB stays `user_version 10` — TASK 47's Source columns are additive guarded ALTERs. Recipe in `MyMistakes.md`. - ChatOverlayLayer.OnMessageReceived marshals the poller seam; AlertOverlayLayer now mirrors it. - Every committed change needs a **close + relaunch** of the running app to be seen. ## Next step 1. Commit the crash fix (declared scope + same-commit docs), then run `./scripts/scope-check.sh "Services/AlertOverlayLayer.cs" "ytLive.Tests/AlertLayerVideoTests.cs"`. 2. Full clean build (0 warnings, Windows dotnet host) + full vstest **320/320**. 3. Push `a11b15e` + the fix commit at the creator's go / milestone signal. 4. Creator to relaunch and re-run the test-tab alerts (one of each) + post a real chat message — the crash is fixed; then confirm which visual element they call the "web-alert box". ## Critical working rules (unchanged, still binding) - **Good Dog = ONE integration test per change.** Scope lock + `./scripts/scope-check.sh` before commit. - Windows dotnet host for all WSL builds (`/mnt/c/Program Files/dotnet/dotnet.exe`, quoted paths). - 0 warnings on real clean builds (verify only, never incremental). One runtime model. - No menus/polls with the creator; decisions asked once then held; do the work, then report.