cb750660c3
Creator: 'when I attempt to refresh my YPP page, I get an error about not being able to reach YouTube. Seriously?' Real log: channels.list failed (403) x3. Root cause #1 (the 403): channels.list?mine=true&part=statistics,auditDetails, contentDetails returns 403 insufficientPermissions when the token lacks the youtubepartner-channel-audit scope — which the auditDetails part ALONE requires, per the docs ('A request that retrieves the auditDetails part ... must provide an authorization token that contains the youtubepartner-channel-audit scope'). That scope is MCN partner tooling with a 2-week token-revocation rule; the app must not hold it. TASK-39's 'current scopes suffice, no re-consent' slice-1 claim was wrong for this part; mock-fake tests never touched the real API, so it shipped green and 403'd every refresh since 2026-09-22. https://developers.google.com/youtube/v3/docs/channels/list Fix: part=statistics,contentDetails only; standing flags removed from ChannelStatsService -> YppStatSnapshot surface -> YppTrackerViewModel -> drawer, replaced by an honest deep-link row ('Channel standing isn't exposed to YouTube apps — check the Earn page'). YppSnapshot standing columns stay (schema-stable, always false). channels.list failures now log the response BODY — the bare code could not name insufficientPermissions, which is what made this undiagnosable. Root cause #2 (found by the new Good Dog, masked by the 403): statistics come back as JSON STRINGS ('350'); raw GetInt64() throws. Tolerant ReadInt64 (ValueKind-first; JsonElement.TryGetInt64 THROWS on strings — type-in, not try-type). Good Dog: ChannelStatsServiceTests.CaptureCurrent_RequestsNoAuditDetails_AndStillParsesTheSnapshot (URL asserts no auditDetails + snapshot parses); YppPullOutTests fixture updated. Recipes for both 403/scope and statistics-strings entered in MyMistakes.md. Also shipped in the same commit (shared PreviewPane.xaml + ai.md): the audio-sync status dot removal from the #77 feedback round (creator: 'what is the point of the status light? Lose it') — IntToSyncBrushConverter deleted with it. verify.sh gate: 0 warnings, 316/316 pass, scope-check clean.
102 lines
6.8 KiB
Markdown
102 lines
6.8 KiB
Markdown
# HANDOFF — 2026-09-23 (build #77 feedback round; LOG fixes all shipped; app likely running)
|
||
|
||
## 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).
|
||
|
||
**Working tree DIRTY — two finished hotfix work units from the live build #77 session:**
|
||
|
||
```
|
||
M Controls/PreviewPane.xaml (audio-sync status dot removed + YPP standing row)
|
||
M Themes/Controls.xaml (IntToSyncBrush converter resource removed)
|
||
D Helpers/IntToSyncBrushConverter.cs (converter dead — deleted)
|
||
M Services/ChannelStatsService.cs (YPP auditDetails part dropped; 403 body logged)
|
||
M Services/YppTrackerViewModel.cs (standing flags removed; honest deep-link text)
|
||
M ytLive.Tests/YppPullOutTests.cs (fixture loses the standing flags)
|
||
?? ytLive.Tests/ChannelStatsServiceTests.cs (NEW Good Dog: no auditDetails in URL + parses)
|
||
M ai.md, TASKS/task-22, TASKS/task-39, TASKS.md, MyMistakes.md, HANDOFF.md
|
||
```
|
||
|
||
## ⚠️ The app is RUNNING, and that's the story of this round
|
||
|
||
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.
|
||
|
||
## 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).
|
||
- 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`
|
||
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.
|
||
|
||
## Next step
|
||
|
||
1. **Close the running app (PID 17752 holds the exe), then run the full gate** —
|
||
`./scripts/verify.sh` with the full declared scope (audio-dot unit + YPP fix): clean build
|
||
0-warnings + full suite + scope check. Then commit BOTH units — they share PreviewPane.xaml /
|
||
ai.md, so a single hotfix commit ("build-77 polish + YPP refresh 403") is the honest shape, or
|
||
two commits if the user prefers strict splits.
|
||
|
||
⚠️ The YPP Good Dog (`ChannelStatsServiceTests`) and the updated `YppPullOutTests` cannot even
|
||
BUILD until the app closes (the app csproj copy fails on the locked exe) — they were written but
|
||
not yet executed.
|
||
|
||
2. After the gate: push decisions pending (user's call). TASK 40 Unit A 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.sh only, never incremental). One runtime model. |