Files
LlamaCasty/HANDOFF.md
T
gramps 0981a72eea fix(stream): chat insert 400 — insert body must declare snippet.type
The liveChatId fix (TASK 44) worked on the next Test Stream, but every Mock
Chat Input send returned 'YouTube rejected the message (error 400)'. The
runtime log's body: 400 MISSING_REQUIRED_FIELD, domain
youtube.api.v3.LiveChatMessageInsertResponse.Error.

The liveChat/messages.insert snippet requires type ('textMessageEvent' or
'pollEvent') alongside liveChatId and textMessageDetails.messageText; the
TASK 41 body omitted it, and the Good Dog test asserted only liveChatId +
messageText were present — false-green while real YouTube rejected every
send. Fix body + assert type in the same test so the field can never drop
silently again.

Reference: https://developers.google.com/youtube/v3/live/docs/liveChatMessages/insert

Good Dog: ONE integration test (strengthened TestStream_DockTooling...).
Gate: clean build 0 warnings; full vstest 317/318 (the 1 failure is the
known environmental RealMouseDrag flake — passes 3/3 in isolation).
2026-09-25 08:12:05 -07:00

74 lines
4.5 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-25 (TASK 45 shipped: TEST-tab insert 400 fix — body was missing `snippet.type`)
## Branch / Commit State
`main` HEAD = new commit for TASK 45 (chat-insert 400 fix); previous: TASK 44 commit
(TEST-tab chat fix), `94f0150`. Repo is ~12 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 (TASK 44 reopened by the live run)
The creator ran the TASK 44 fix and the drawer now worked — but every Mock Chat Input
send returned **"YouTube rejected the message (error 400)"**. The runtime log
(`%APPDATA%\ytLlive\startup.log`) carried the body: `400 MISSING_REQUIRED_FIELD`,
`domain: youtube.api.v3.LiveChatMessageInsertResponse.Error`.
- **Root cause:** the `liveChat/messages.insert` body must declare **`snippet.type`**
(`textMessageEvent` or `pollEvent`) alongside `liveChatId` and
`textMessageDetails.messageText` — the official insert reference lists `type` as a
required property. The TASK 41 body sent only `liveChatId` + `textMessageDetails`.
- **Why the suite didn't catch it:** the TASK 41 Good Dog test asserted the POST body
contained `liveChatId` + `messageText` but **never `type`** — false-green while real
YouTube rejected every send. The fix asserts `type` too, so a dropped field fails now.
- **Fix (TASK 45, `TASKS/task-45-chat-insert-type.md`):** `InsertChatMessageAsync` adds
`type = "textMessageEvent"` to the body; test assertion strengthened; recipe added to
`MyMistakes.md` (MISSING_REQUIRED_FIELD = wrong body shape, not auth).
**Good Dog:** ONE integration test — the strengthened
`TestStream_DockTooling_And_ThreeWayDrawerExclusivity` POST-body assertion.
**Gate: clean build 0 warnings; full vstest 317/318 — the 1 failure is the known
environmental `RealMouseDrag` flake (mouse stolen by a fullscreen window mid-suite);
it PASSES in isolation (3/3), confirmed just now, so our change is green.**
## Follow-ups queued (NOT done in this unit)
- **TASK 3 item 20 persistence half** — the canonical `RewardEvents` SQLite table +
`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)
- 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**; the single test
passes in isolation when re-run alone. 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. `$99/yr` Polar
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`.
- **API facts (TASK 44 + 45):** a liveChatId fetch at broadcast-insert time (lifecycleStatus
`ready`) returns nothing by design — missing liveChatId ≈ "broadcast not live yet", not an
auth failure, until ~20s post push. And `400 MISSING_REQUIRED_FIELD` from
`liveChat/messages.insert` = wrong body shape (missing `snippet.type`), NOT auth/scope.
- Every committed change needs a **close + relaunch** of the running app to be seen — running
builds are live; verify can't run while the exe is locked.
## Next step
1. **Creator verifies the full TEST-tab chat path on the next run**: Test Stream → drawer
opens with a liveChatId; Mock Chat Input enabled ~as soon as the broadcast goes live;
send a message — it should now insert (200) and round-trip through the ~2s poll onto the
overlay. This completes the original creator report ("I still cannot post a chat message
in the TEST tab").
2. Push decision is the creator's (repo ~12 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)
- **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.