fix(stream): health poll crashed on every start — parse the real healthStatus object shape

startup.log 2026-09-22 (via the user's run of the new build): every session
start logged 'Stream health poll failed: The requested operation requires an
element of type String, but the target element has type Object'.

Root cause: GetStreamHealthAsync did status.healthStatus.GetString(), but the
real API nests it as status.healthStatus = {status, lastUpdateTimeSeconds,
configurationIssues[]} — an OBJECT, per the liveStreams reference
https://developers.google.com/youtube/v3/live/docs/liveStreams (checked the
docs before fixing, per derivative-work rule). Our flat-string fixture was the
wrong assumption since TASK 9; the report-by-exception health banner had been
silently dead.

Fix: read the nested healthStatus.status + nested configurationIssues[], tolerating
the legacy flat-string shape so nothing else breaks. Tests flipped to the real
object shape (the Good Dog for this change). Full suite 315/315, build 0 warnings.

Also recorded in MyMistakes.md (API-shape recipe) and ai.md/Services index. End-of-session
transition(complete) 403 invalidTransition (enableAutoStop fallback) observed on all three
2026-09-22 stops — separate racy-design issue, carried, not in this change.
This commit is contained in:
2026-09-22 19:25:02 -07:00
parent e69db4d26c
commit 18ab553b0e
6 changed files with 94 additions and 50 deletions
+22 -19
View File
@@ -1,30 +1,18 @@
# HANDOFF — 2026-09-22 (TASK 42 top bar redesign implemented; commit + full-suite rerun owed)
# HANDOFF — 2026-09-22 (TASK 42 committed; health-poll parse fix in flight)
## Branch / Commit State
`main` HEAD = **`bb5dcb4` — feat(stream): TASK 41 Test Stream mode …** (TASK 41 committed).
Pushed state at `197ee81`. **Working tree DIRTY — TASK 42 implementation done, not yet committed**
(docs updated in this same pass; next step is scope-check + build + full tests + one commit):
`main` HEAD = **`e69db4d` — feat(ui): TASK 42 top bar redesign …** (committed).
Pushed state at `197ee81` (verified from the app-build the user ran after the commit). **Working
tree DIRTY — health-poll parse bug fix, one small change (code + tests + docs):**
```
M Controls/BottomBar.xaml(.cs) (gear removed)
M Controls/TopBar.xaml(.cs) (rewrite + GearButton_Click)
M Themes/Controls.xaml (SegmentToggle/SegmentLabel)
M ViewModels/MainViewModel.Streaming.cs
M ViewModels/MainViewModel.Account.cs
M ViewModels/MainViewModel.Streaming.Operations.cs
M ytLive.Tests/PillRadioTests.cs / TestStreamTests.cs
?? ytLive.Tests/TopBarModeTests.cs (new Good Dog)
M TASKS.md + TASKS/task-42-top-bar-redesign.md (new)
M ai.md, ViewModels/index.md, Controls/index.md, TASKS/task-40-app-settings-round.md
M Services/YouTubeStreamService.cs (parse fix)
M ytLive.Tests/YouTubeStreamServiceTests.cs (fixtures flipped to REAL api shape)
M ai.md, Services/index.md, MyMistakes.md
M HANDOFF.md (this rewrite)
```
Build impact: main + tests build clean, **0 warnings**; targeted suite **6/6 pass**; full-suite run
**314/315 pass** — the single abort is the known environment-dependent `RealMouseDrag` test (Path of
Exile 2 running, physical-mouse injection no-ops; see ⚠️ below and rerun with the game closed for a
clean 315/315).
## What shipped — TASK 42: top bar redesign (2026-09-22)
Full record: **`TASKS/task-42-top-bar-redesign.md`**. Creator directive: "forget this one dog plan
@@ -53,6 +41,21 @@ changing + running reality values. Also folded in: TASK 41's latent **Test pipel
`BeginTestStream` now arms `OnAirPillOn` explicitly (unarmed → encoder booted with zero outputs →
"At least one output is required" → forced stop cascade). The TestStreamTests gate updated to match.
## In flight — health-poll parse fix (2026-09-22, from startup.log)
The user ran the new build and the app's `startup.log` showed `Stream health poll failed: The
requested operation requires an element of type 'String', but the target element has type 'Object'.`
on EVERY session start (2/2 test sessions). Root cause: `YouTubeStreamService.GetStreamHealthAsync`
called `healthStatus.GetString()` but the real API nests `status.healthStatus = {status,
lastUpdateTimeSeconds, configurationIssues[]}` — an OBJECT, not a string (docs:
https://developers.google.com/youtube/v3/live/docs/liveStreams). Our own flat-string fixture was the
wrong assumption from day one; the report-by-exception health banner had been silently dead. Fix in
the dirty tree reads the nested `.status` and nested `configurationIssues[]`, tolerating the old flat
shape; fixtures flipped to the real shape (the Good Dog). ALSO logged: end-of-session
`transition(complete)` 403 `invalidTransition` → enableAutoStop fallback hit all three 2026-09-22
stops (17:09 crash + both tests) — known racy design (transition AFTER encoder EOF), NOT in this fix's
scope, carried.
## ⚠️ Around the task
- **Full-suite run today: 314/315** — the one abort is the pre-existing