feat(alerts): TASK 43 — native events & alerts (six unique animations) + chat parity
Replace the external StreamElements feed with NATIVE YouTube events per the creator's 2026-09-23 question. Chat parity half: - YouTubeChatService decodes ALL SIX liveChat/messages event types into ChatMessage.Kind (superChat/superSticker/newSponsor/membershipGifting/ giftMembershipReceived/memberMilestoneChat) — the four formerly-empty overlay rows are real now — and re-arms its one-shot poll on the server's pollingIntervalMillis (streamList connection semantics; clamp 1000-6000ms, maxResults=2000). ParsePage internal static seam + ChatPage record for deterministic tests; optional HttpClient ctor seam kept; InjectSimulatedMessage (TASK 41) preserved. Alerts half (creator rulings: one celebration zone, six UNIQUE animations, no menus/polls, sub mention = chat row only, NO viewer count): - New SourceType.AlertBox 'Stream Alerts' (one per layout, CanAddAlerts gate mirroring chat; idle = transparent). - AlertRenderer: 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), every card drawing the 'made with LlamaCasty!' brand line. - AlertOverlayLayer: true component (Commit-G pattern) — queue (cap 10, drop tail never stall), 33ms UI ticker, cache-first RenderFrame + UpdatePreview, Advance(double) as the deterministic test clock; ChatEventKind.None rows never enqueue. - Wired: resolver RenderAlertBox, _alertLayer ctor + dispose, LoadLayout previews, Add menu item (Controls/LeftPanel.xaml), TestSessionViewModel sims tagged (member->NewMember, superchat->SuperChat). Good Dog ONE integration test: AlertLayerTests (RealApp STA, real WPF raster) — ParsePage classifies all six kinds + cadence fields; None rows enqueue nothing; six events play pairwise-distinct moving frames then drain to null. Gate: clean build 0 warnings; full suite 316/317, the one failure (LayerReorderPersistenceTests.RealMouseDrag) repros on the clean tree — the known environmental class (real-mouse-drag no-ops with a game/fullscreen window focused). References (OBS/overlay ecosystem): - streamList semantics: https://developers.google.com/youtube/v3/live/docs/liveChatMessages/streamList - OBS alert-box pattern (designated celebration zone, idle transparent): creator-chosen model
This commit is contained in:
+58
-80
@@ -1,103 +1,81 @@
|
||||
# HANDOFF — 2026-09-23 (build #77 feedback round; LOG fixes all shipped; app likely running)
|
||||
# HANDOFF — 2026-09-24 (TASK 43 shipped: native events & alerts — creator said "go")
|
||||
|
||||
## Branch / Commit State
|
||||
|
||||
`main` HEAD = **`d72949e`** (build-77 top-bar feedback: avatar/gear/cluster). Backlog:
|
||||
`ac6e67a` (end close-out), `18ab553` (health-poll), `e69db4d` (TASK 42), `bb5dcb4` (TASK 41).
|
||||
Pushed state at `197ee81` (36 commits ahead locally — push only at a sub-milestone / on the
|
||||
user's say-so).
|
||||
`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).
|
||||
|
||||
**Working tree DIRTY — account zone made world-independent (creator 2026-09-23):**
|
||||
**Working tree CLEAN after this unit's commit.**
|
||||
|
||||
```
|
||||
M ViewModels/MainViewModel.Streaming.cs (ShowSignInButton = !IsConnected, ShowAvatarButton = IsConnected)
|
||||
M ytLive.Tests/TopBarModeTests.cs (REC-world asserts: avatar stays, Sign In visible signed-out)
|
||||
M ai.md, TASKS/task-42, HANDOFF.md
|
||||
```
|
||||
## ⚠️ The unit that just shipped (this turn, creator said "go")
|
||||
|
||||
Previous two hotfix units (audio-sync dot + YPP 403/statistics-string) are COMMITTED (`cb75066`);
|
||||
top-bar feedback COMMITTED (`d72949e`).
|
||||
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 account-zone unit (this turn)
|
||||
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.
|
||||
|
||||
Creator, clicking RECORD on the live build: "why do you make the youtube user icon go away?" — the
|
||||
TASK 42 one-surface bar had scoped the account zone to the Stream world ("Record world has no YouTube
|
||||
identity at all"), so arming REC dropped the avatar AND the bar height bounced with it ("really fucking
|
||||
annoying"). Fix: `ShowSignInButton`/`ShowAvatarButton` are now world-independent (`!IsConnected` /
|
||||
`IsConnected`) — the right column never pops in/out, bar height pinned. Test stays a child of Stream.
|
||||
`TopBarModeTests` updated: signed-out Record world shows Sign In; flipping back to REC keeps the avatar.
|
||||
**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.**
|
||||
|
||||
## ⚠️ The app is RUNNING, and that's the story of this round
|
||||
## Follow-ups queued (NOT done in this unit)
|
||||
|
||||
Ac6e67a **could not be verified through the normal gate** because the user was *looking at the
|
||||
running app* (PID 17752) while it happened. `verify.sh` (clean build 0-warnings + full suite +
|
||||
scope check) **cannot run until the app is closed** — an in-place build fails the `apphost.exe`
|
||||
copy MSB3021/MSB3027 while the exe is locked. Compile gate used instead: `dotnet build` reaches
|
||||
the copy stage with XAML/markup compiling clean (only the 2 copy errors → code + XAML are valid).
|
||||
|
||||
## What the user asked this round (live-launch feedback on #77, all XAML-only)
|
||||
|
||||
1. **Avatar** much bigger — now 40×40 (was 26), CornerRadius 20, initial 20pt. Shows the streaming
|
||||
account at a glance (113% bigger than the old 26 read).
|
||||
2. **Gear** moved further from the brand — margin 6 → 18 (a couple more "places").
|
||||
3. **Uniform 32-height cluster in the top center** — segments host Border, Start/Go Live, and Test
|
||||
all `Height="32"` so the REC|ON-AIR switch + its actions read as ONE family ("so the user can
|
||||
intuit their purpose"). Running world (reality line) intentionally untouched. **[COMMITTED `d72949e`]**
|
||||
4. **Audio-sync status light LOST** — `"AUDIO SYNC"` slider keeps its numeric tooltip; the green/
|
||||
amber 8×8 dot was "what is the point" dead weight. `IntToSyncBrushConverter` + its resource +
|
||||
TASK-22's "visual feedback" decision all retired with it, both docs updated in-place.
|
||||
5. **YPP refresh bug (this turn, "Seriously?")** — `channels.list?mine=true&part=statistics,
|
||||
auditDetails,contentDetails` 403'd `insufficientPermissions` on EVERY real refresh since slice 1
|
||||
shipped: the **`auditDetails` part alone requires the `youtubepartner-channel-audit` scope**
|
||||
(MCN partner tooling the app must never hold; the "no re-consent / scopes suffice" slice-1 claim
|
||||
was wrong). Fix: part list is now `statistics,contentDetails`; standing flags dropped from
|
||||
snapshot→VM→drawer in favour of an honest "not exposed to apps — check the Earn page" row;
|
||||
`channels.list` failures now log the response body's `error.reason` (the bare code hid this for
|
||||
days); new Good Dog `ChannelStatsServiceTests` guards no-auditDetails + still-parses — which it
|
||||
then used to catch a SECOND latent bug the 403 masked (statistics are JSON strings; `GetInt64`
|
||||
throws → now the tolerant `ReadInt64`, ValueKind-first). Docs:
|
||||
ai.md / TASK-39 / TASKS.md / MyMistakes all corrected. Cite:
|
||||
https://developers.google.com/youtube/v3/docs/channels/list
|
||||
|
||||
## What shipped before this round (all committed)
|
||||
|
||||
- **`ac6e67a` end-of-stream close-out fix** — every 2026-09-22 stop logged
|
||||
`transition(complete) 403 invalidTransition`; the blind POST raced YouTube/autoStop marking the
|
||||
broadcast complete (`enableAutoStop=true` always set). `EndBroadcastAsync` now pre-flights
|
||||
`liveBroadcasts.list→status.lifeCycleStatus` and skips the POST when already complete/revoked;
|
||||
inconclusive pre-check still posts (backstopped); never throws. Good Dog:
|
||||
`EndBroadcast_Verifies_LifeCycle_Then_Transitions_Complete_Never_Throws` (3 halves: live→GET+POST,
|
||||
complete→skip, 403→error-string). Cite: youtube liveBroadcasts/transition errors table. **Full
|
||||
suite 315/315** (audio-timing flake `Mix_HonorsProviderGains…` cleared on rerun); verify.sh clean
|
||||
pass predates this round's XAML-only edits.
|
||||
- **`18ab553` health-poll fix** — `status.healthStatus` is an OBJECT, not a string; every session
|
||||
start logged `requires an element of type 'String'`. Nested shape parsed (flat tolerated), banner
|
||||
alive again.
|
||||
- **TASK 42** (`e69db4d`) + **TASK 41** (`bb5dcb4`) — one-surface top bar, Test Stream drawer.
|
||||
- **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).
|
||||
- TASK 3 item 16 (Text source) still open; TASK 40 units A/C/D queued; TASK 32–36 queued.
|
||||
|
||||
## Around the task (carried facts)
|
||||
|
||||
- RealMouseDrag test no-ops while a game/fullscreen window steals the mouse (POE 2 seen 2026-09-22)
|
||||
— close games before full-suite runs. AudioPipeline timing flake similar (load-dependent).
|
||||
- RealMouseDrag tests no-op while a game/fullscreen window steals the mouse (POE 2 seen
|
||||
2026-09-22) — close games before full-suite runs. AudioPipeline timing flake similar.
|
||||
- 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. Polar `$99/yr`
|
||||
- `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.
|
||||
- The **`YppSnapshot.OverallGoodStanding` etc. columns now serialize false forever** (the API path
|
||||
no longer populates them; schema kept stable for the analytics slice). Don't "restore" the
|
||||
standing flags via auditDetails — see the MyMistakes recipe.
|
||||
- TASK 40 App Settings round: units **A (camera) → C (defaults) → D (accent)** remain; A's
|
||||
SharedReadOnly control-write question = only genuine uncertainty.
|
||||
- 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).
|
||||
- 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. **Run the gate on the account-zone unit** (declared scope: `ViewModels/MainViewModel.Streaming.cs`,
|
||||
`ytLive.Tests/TopBarModeTests.cs`, `ai.md`, `TASKS/task-42-top-bar-redesign.md`, `HANDOFF.md`) —
|
||||
`./scripts/verify.sh`: clean build 0-warnings + full suite + scope check. Then commit it
|
||||
("account zone is world-independent — avatar stays in Record world", code + docs in one).
|
||||
Repo is 36 commits ahead of pushed `197ee81`; push decision is the creator's (sub-milestone).
|
||||
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.
|
||||
|
||||
## 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.sh only, never incremental). One runtime model.
|
||||
- 0 warnings on real clean builds (verify.sh only, never incremental). One runtime model.
|
||||
- No menus/polls with the creator; decisions asked once then held; do the work, then report.
|
||||
Reference in New Issue
Block a user