fix(stream): TEST-tab chat — resolve liveChatId from snippet, poll until the broadcast is live
The open creator report ('I still cannot post a chat message in the TEST tab',
feedback 'Chat polling couldn't start — Mock Chat Input is disabled') was a single
cause: the liveChatId never resolved. Two YouTube API facts:
1. The id lives in snippet.liveChatId — contentDetails has no such property
(TASK 44 read part=contentDetails: could never resolve).
https://developers.google.com/youtube/v3/live/docs/liveBroadcasts
2. It only exists once the broadcast is LIVE — the official sample lists
broadcastStatus=active, and our fetch ran before the frame pump pushed RTMP
(enableAutoStart flips ready→live). A ready-state list legitimately returns
no id.
https://github.com/youtube/api-samples/blob/master/java/src/main/java/com/google/api/services/samples/youtube/cmdline/live/GetLiveChatId.java
Fix: GetBroadcastLiveChatIdAsync reads part=snippet and polls with a bounded
retry (10x/2s); PrepareAndStartLiveAsync starts the frame pump FIRST, then
resolves the id. Chat stays non-fatal. Docs ai.md/TASKS.md/HANDOFF.md +
MyMistakes recipe updated same commit. TASK 44.
Good Dog: ONE integration test (GetBroadcastLiveChatIdAsync_Polls_Snippet_...
) drives a broadcast that gains its id mid-retry and asserts every request used
part=snippet. Gate: clean build 0 warnings, vstest 318/318.
This commit is contained in:
+33
-45
@@ -1,52 +1,41 @@
|
||||
# HANDOFF — 2026-09-24 (TASK 43 shipped: native events & alerts — creator said "go")
|
||||
# HANDOFF — 2026-09-25 (TASK 44 shipped: TEST-tab chat fix — liveChatId now resolves)
|
||||
|
||||
## Branch / Commit State
|
||||
|
||||
`main` HEAD = new commit for TASK 43 (native events & six-animation alert box); previous:
|
||||
`3a4e17c` (account zone world-independent), `cb75066` (YPP 403), `d72949e` (top-bar feedback),
|
||||
`ac6e67a` (end close-out). Repo is ~10 commits ahead of pushed `197ee81` — **push is the
|
||||
creator's call** (sub-milestone decision, not per-commit).
|
||||
`main` HEAD = new commit for TASK 44 (TEST-tab chat fix); previous: TASK 43 commit (native
|
||||
events & alerts), `ac6e67a` (end close-out). Repo is ~11 commits ahead of pushed `197ee81` —
|
||||
**push is the creator's call** (sub-milestone decision, not per-commit).
|
||||
|
||||
**Working tree CLEAN after this unit's commit.**
|
||||
|
||||
## ⚠️ The unit that just shipped (this turn, creator said "go")
|
||||
## ⚠️ The unit that just shipped (the open TEST-tab chat report, diagnosed + fixed)
|
||||
|
||||
The creator asked (2026-09-23) whether the app could use **native YouTube events instead of
|
||||
StreamElements**. Research pinned the API facts; creator rulings locked the product shape. One
|
||||
combined work unit ("go"), task filed as **TASK 43** (`TASKS/task-43-native-alerts.md`):
|
||||
The HANDOFF-open creator report — *"I still cannot post a chat message in the TEST tab"* —
|
||||
is **RESOLVED** (one cause, one scoped unit, TASK 44 in `TASKS/task-44-test-chat-fix.md`):
|
||||
|
||||
1. **Chat parity half** — `YouTubeChatService` now decodes **all six** `liveChat/messages`
|
||||
event types into `ChatMessage.Kind` (the four previously-empty event rows are real now) and
|
||||
re-arms its poll on the server's `pollingIntervalMillis` (streamList semantics; clamp
|
||||
1000–6000ms, `maxResults=2000`).
|
||||
2. **Alerts half** — new **`SourceType.AlertBox`** ("Stream Alerts", one per layout,
|
||||
`CanAddAlerts` gate mirroring chat): an OBS-style celebration zone, idle = transparent.
|
||||
`Services/AlertRenderer.cs` draws **six DISTINCT branded animations** (SuperChat slide-up/
|
||||
shine/count-up; SuperSticker scale-pop; NewMember drop-in/flash; MemberGift slide-left/chip-fan;
|
||||
GiftReceived confetti; MemberMilestone rise/growth-bar), each card carrying the "made with
|
||||
LlamaCasty!" brand line (free product placement per broadcast). `Services/AlertOverlayLayer.cs`
|
||||
is a true component (Commit-G pattern): queue (cap 10) + 33ms ticker + cache-first
|
||||
`RenderFrame` + `UpdatePreview`; **`Advance(double)` = deterministic test clock**.
|
||||
3. **Creator rulings baked in:** no menus/polls (asked once, held); free-sub mention = chat row
|
||||
only, **no sub alert** (YouTube emits none); **no viewer count** (vetoed — demotivating,
|
||||
weaponizable); six events = six unique animations, not a recolored template.
|
||||
- **The feedback line** "Chat polling couldn't start — Mock Chat Input is disabled" is
|
||||
`TestSessionViewModel.BeginTest` running with a **null liveChatId**.
|
||||
- **Root cause (two API facts, cited in commit + MyMistakes):** (1) `liveChatId` lives in
|
||||
**`snippet.liveChatId`** — `contentDetails` has no such property, and the old code read
|
||||
`part=contentDetails` (can NEVER resolve); (2) YouTube only populates it once the broadcast
|
||||
is **live** (official `GetLiveChatId.java` lists `broadcastStatus=active`), and the fetch
|
||||
ran before the frame pump pushed RTMP (enableAutoStart flips ready→live).
|
||||
- **Fix:** `GetBroadcastLiveChatIdAsync(broadcastId, maxAttempts=10, delayMs=2000)` now reads
|
||||
`part=snippet` and polls with a bounded retry; `PrepareAndStartLiveAsync` starts the frame
|
||||
pump FIRST, then resolves the id. Chat stays non-fatal. TEST drawer wiring unchanged
|
||||
(`BeginTest` opens the dock once the id resolves → Mock Chat Input becomes sendable).
|
||||
|
||||
**Good Dog:** `ytLive.Tests/AlertLayerTests.cs` (RealApp STA, real WPF raster) — ParsePage
|
||||
classifies all six kinds + cadence fields; playback never enqueues a `None` (sub-mention) row,
|
||||
plays six pairwise-distinct moving frames, drains to idle. **Gate: clean build 0 warnings,
|
||||
316/317** — the ONE failure (`LayerReorderPersistenceTests.RealMouseDrag…`) repros on the CLEAN
|
||||
tree (proved via `git stash` + rebuild + isolated run): a real-mouse-drag test that no-ops while
|
||||
a game/fullscreen window steals the mouse (the HANDOFF-documented environmental class).
|
||||
**NOT a regression — do not chase it in-app; close games and rerun if in doubt.**
|
||||
**Good Dog:** ONE integration test in `YouTubeStreamServiceTests.cs`
|
||||
(`GetBroadcastLiveChatIdAsync_Polls_Snippet_Until_Id_Appears`) — a fake broadcast that starts
|
||||
without a liveChatId and gains it on a later poll; asserts the retry resolves it, polled ≥3x,
|
||||
and every request asked for snippet (never contentDetails). **Gate: clean build 0 warnings,
|
||||
full vstest 318/318 passed** (the LayerReorder real-mouse-drag test passed this run too).
|
||||
|
||||
## Follow-ups queued (NOT done in this unit)
|
||||
|
||||
- **Creator report still open — not yet diagnosed:** "I still cannot post a chat message in the
|
||||
TEST tab." The TEST drawer's Mock Chat Input posts a REAL `liveChat/messages.insert`; the fix
|
||||
needs its own scoped unit.
|
||||
- **TASK 3 item 20 persistence half** — the canonical `RewardEvents` SQLite table +
|
||||
`superChatEvents.list` (30-day) backfill + session-report rollup (the shipped alert path parses
|
||||
events to `ChatMessage` in memory; the report reads that table once it exists).
|
||||
`superChatEvents.list` (30-day) backfill + session-report rollup (events still parse to
|
||||
`ChatMessage` in memory only).
|
||||
- TASK 3 item 16 (Text source) still open; TASK 40 units A/C/D queued; TASK 32–36 queued.
|
||||
|
||||
## Around the task (carried facts)
|
||||
@@ -58,20 +47,19 @@ a game/fullscreen window steals the mouse (the HANDOFF-documented environmental
|
||||
must become one-time before launch. `MARCOM.md`/`MONETIZATION.md` gitignored — never commit.
|
||||
- Latent bug the YPP 403 masked: statistics are JSON **strings** — always the ValueKind-first
|
||||
`ReadInt64` (MyMistakes recipe), never `GetInt64`.
|
||||
- New facts recorded: the chat feed IS the event feed (six types, no sub event); streamList is a
|
||||
connection-semantics description of that same endpoint (pollingIntervalMillis drives cadence).
|
||||
- **API fact (TASK 44):** a liveChatId fetch at insert time (lifecycleStatus `ready`) returns
|
||||
nothing by design — missing liveChatId ≈ "broadcast not live yet", not an auth failure,
|
||||
until ~20s post push. Debug with that lens.
|
||||
- Every committed change needs a **close + relaunch** of the running app to be seen — running
|
||||
builds are live; verify.sh can't run while the exe is locked.
|
||||
|
||||
## Next step
|
||||
|
||||
1. **Creator verifies the alerts unit on the next run**: Add → Stream Alerts, then (Test Stream
|
||||
drawer) Simulated Super Chat / New Member — confirm the zone stays transparent at idle and each
|
||||
event plays its own animation on the canvas and preview; a real Super Chat during the next live
|
||||
run confirms the real path.
|
||||
2. Push decision is the creator's (repo ~10 ahead). If pushed, note the commit id in this file.
|
||||
3. When scoped: diagnose the TEST-tab chat posting report ("I still cannot post a chat message in
|
||||
the TEST tab") as its own Good-Dog unit.
|
||||
1. **Creator verifies the TEST-tab chat fix on the next run**: Test Stream → the drawer should
|
||||
open with a liveChatId line and Mock Chat Input enabled ~as soon as the broadcast goes live;
|
||||
send a message and confirm it round-trips through the ~2s poll and renders on the overlay.
|
||||
2. Push decision is the creator's (repo ~11 ahead). If pushed, note the commit id in this file.
|
||||
3. When scoped: TASK 3 item 20 (RewardEvents persistence half) draws next.
|
||||
|
||||
## Critical working rules (unchanged, still binding)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user