# TASK 43 — Native events & alerts (the "go" unit: chat parity + six animation alert box) > Catalog: [`TASKS.md`](../TASKS.md). Status: ✅ **SHIPPED 2026-09-24** — creator said > **"go"** on the combined work unit (native-chat parity + native alert box). See > [`ai.md`](../ai.md) → "Native events & alerts" for the architecture. ## Provenance (recorded per the feature-provenance rule) - **2026-09-23, the creator asked** — "Could you use native YouTube events instead of StreamElements?" (the app was designed to feed an external StreamElements alert box). Research (websearch + the liveChat/messages + streamList docs, URLs in the commit message) established the API facts below; the creator then ruled on product shape. - **Decisions the creator made in conversation (asked ONCE, then held):** 1. **No menus / no polls** — "I've asked you before to not use menus/polls." Decisions are asked on the open floor once and then executed. 2. **Events = ONE animated alert box** (OBS alert-box pattern), a designated area on the canvas where celebration animations play. NOT a list pane, NOT per-source. 3. **Six monetization events → SIX UNIQUE animations** — "not cookie-cutter": each of Super Chat, Super Sticker, New Member (newSponsor), Member Gift, Gift Received, Member Milestone gets its own distinct gesture, palette and layout. Each animation is a free branded product-placement ad in the stream (the brand line is drawn into every card) and a celebration of the supporter's investment. 4. **Free subscribers: NO alert** — "subscriber mention in chat is sufficient." YouTube exposes no public event for free subs (API fact); a mention arrives as a normal chat row and is deliberately `ChatEventKind.None` → the alert engine never fires. The app keeps its opinion: none. (The third-party socket + delta-counter folklore was explicitly rejected with it.) 5. **Viewer count: dropped entirely** — demotivating and weaponizable against the creator; not even hidden. 6. **Native chat replaces the SE URI** — the chat box already exists in-app (`SourceType.ChatBox`, fed by real `liveChat/messages` polling). The remaining parity gaps closed here: the four previously-undecoded event types displayed as **empty rows** in the overlay, and the poll cadence was a hardcoded 2s instead of the server's `pollingIntervalMillis`. ## API facts (researched + cited in commit) - Native event feed = the **same** `liveChat/messages` endpoint already polled. `streamList` is a *connection semantics* description of that endpoint, not a separate call: response carries `pollingIntervalMillis`; reconnect with `nextPageToken` on that cadence, never faster (YouTube rejects rapid polling). The code now re-arms the one-shot poll on the server's number (clamped 1000–6000ms), and `maxResults=2000`. Cite: https://developers.google.com/youtube/v3/live/docs/liveChatMessages/streamList - Six `snippet.type` event kinds exist: `superChatEvent`, `superStickerEvent`, `newSponsorEvent`, `membershipGiftingEvent`, `giftMembershipReceivedEvent`, `memberMilestoneChatEvent`. Free subscriptions emit **nothing** — no API event, no delta, no count (the "newSponsorEvent counts" folklore is wrong for public subs). - `videos.list → liveStreamingDetails.concurrentViewers` exists but was **vetoed** by the creator (decision 4 above). ## Product model (as shipped) - **`SourceType.AlertBox`** ("Stream Alerts") — a one-per-layout celebration zone, mirroring the chat layer's gate (`CanAddAlerts` = no AlertBox exists in any scene; `AddSource` refuses a second). Defaults 620×430 → 680×200. Draggable, previews like a source, idle = fully transparent (nothing on stream until an event actually fires). - **Playback:** events from the chat feed enqueue one-at-a-time on a 33ms UI dispatcher tick, play their own animation (4–5s each), then drain; back-to-back floods cap the queue at 10 and drop the tail, never stalling playback. The clock is exposed as `Advance(double)` so tests drive playback deterministically with no timer. - **The six animations** (`Services/AlertRenderer.cs`), phases enter[0–0.16] / hold[0.16–0.8] / exit[0.8–1]: | Kind | Gesture | Palette | |---|---|---| | SuperChat | slide-up + gold amount count-up + shine sweep | green `#0d3b2b→#14532d` | | SuperSticker | scale-pop overshoot, tilted sticker chip, bob | purple `#4a044e→#7e22ce` | | NewMember | drop-in + white flash band + gold level chip | violet `#2e1065→#5b21b6` | | MemberGift | slide-left + gift-chip fan `×N` | amber `#422006→#a16207` | | GiftReceived | confetti burst scale-in | green→purple `#14532d→#4c1d95` | | MemberMilestone | rise-up + tenure growth bar | orange `#431407→#c2410c` | - Every card draws **"made with LlamaCasty!"** bottom-right — each broadcast of an event is in-video product placement (creator decision: "it's a free ad each time"). ## Chat parity (half 1 of the unit) - `YouTubeChatService` rewritten around the real cadence + all six event parses: `ParsePage(string)` (internal static seam) → `ChatPage(Messages, NextPageToken, PollIntervalMs)`; fields landed on `ChatMessage`: `Kind` (`ChatEventKind`), `SuperChatDisplayString`, `SuperStickerDescription`, `GiftNewMembersCount`, `GifterName`, `MilestoneMonths` (best-effort `(\d+) months` regex from the milestone chat text). The four formerly-empty event types now parse as real rows AND drive animations. `InjectSimulatedMessage` (TASK 41) preserved. ## Test (Good Dog — ONE integration test per change) `ytLive.Tests/AlertLayerTests.cs` (RealApp STA host, real WPF raster) — `AlertLayerTests`: 1. **Parse:** a mocked 7-item feed → all six kinds classified, `None` for plain chat, `pollingIntervalMillis`/`nextPageToken` honored, fields verified (incl. the formerly-empty `3 memberships` / `gifted you a membership` rows). 2. **Playback:** subscriber-mention (`None`) enqueues nothing; the six events play through the queue with non-null, non-blank, *moving* frames; every event's mid-hold frame is **pairwise distinct from every other** (six unique animations, pinned); playback drains to transparent/null. ## Files (declared scope) - `Models/YouTube.cs` (ChatEventKind + fields), `Models/Source.cs` (SourceType.AlertBox + IsDraggable/Kind/DisplaySource) - `Services/YouTubeChatService.cs` (cadence + six-event ParsePage seam) - `Services/TestSessionViewModel.cs` (sims tagged: member→NewMember, superchat→SuperChat) - **NEW** `Services/AlertRenderer.cs`, `Services/AlertOverlayLayer.cs` - `ViewModels/MainViewModel.cs`, `ViewModels/MainViewModel.Chat.cs`, `ViewModels/MainViewModel.Sources.cs` (wiring: `_alertLayer`, render resolver, `CanAddAlerts`, menu name, shakedown `LoadLayout`, dispose) - `Controls/LeftPanel.xaml` (Add → Stream Alerts menu item) - **NEW** `ytLive.Tests/AlertLayerTests.cs` - Docs: `ai.md`, `TASKS.md`, this file, `Services/index.md`, `ViewModels/index.md`, `Models/index.md`, `TASKS/task-03-capture-pipeline.md`, `HANDOFF.md` ## Gate Clean build 0 warnings (Windows dotnet host); full suite **316/317** — the one failure `LayerReorderPersistenceTests.RealMouseDrag…` repros on the CLEAN tree (proved via `git stash`; it is a real-mouse-drag test that no-ops when a game/fullscreen window steals the mouse — HANDOFF-documented environmental class). Scope check green. ## Open follow-ups (NOT this unit) - TASK 3 item 17 (Alerts) is DONE via native events; **item 20 stays open** — the RewardEvent SQLite persistence + session-report rollup + `superChatEvents.list` backfill were scoped as the capture foundation and remain queued (the shipped alert path parses events directly into `ChatMessage`, which is what the report reads). - TASK 3 item 16 (Text source) still open. - Creator report still open: "I still cannot post a chat message in the TEST tab" — not yet diagnosed; the TEST drawer's Mock Chat Input posts via a real insert.