Files
LlamaCasty/HANDOFF.md
T
gramps 7b940b6a5a fix(alerts): AlertOverlayLayer marshals the chat-poller seam to the UI thread
Live test session proved the native alert box DID play (the alert ring was the
only source in the mix: micLevel/loopLevel 0.000 while peakMix went
0.375->0.733->0.891 after the sim injections) but the app crashed at
11:22:01.301 the moment a REAL message round-tripped through the poller:

  System.ArgumentException: Must create DependencySource on same Thread as
  the DependencyObject
  at ...MS.Internal.Data.DataBindEngine.ProcessCrossThreadRequests()

OnMessageReceived ran RefreshAlertPreviews on the MTA poller thread and
stamped the WPF-bound VideoImageSource with a WriteableBitmap created there;
the binding engine's cross-thread re-bind killed the process. ChatOverlayLayer
already marshals this exact seam (Dispatcher.Invoke) - mirror it, guarded for
Application.Current null so the pure test seams still run inline.

Good Dog test:
AlertLayerVideoTests.OnMessageReceived_FromPollerThread_MarshalsPreviewWritesToTheUiThread
calls the seam from a raw MTA Thread while the RealApp loop runs, then asserts
on the UI thread that the preview bitmap's Dispatcher is the App's (red on the
old seam, green on the fix). WPF cross-thread rule recorded in MyMistakes.md.

Full suite 320/320, clean build 0 warnings, scope-check green.
2026-09-26 11:28:36 -07:00

82 lines
5.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.