a9d5f29084
Current-OAuth-scope data, no re-consent: subscriber bars for both tiers (500 fan-funding / 1,000 ad-revenue; the 2027-02-01 doubling is versioned date-aware data in YppThresholds), 3-uploads/90d, live standing from channels.list auditDetails, self-reported 2FA/AdSense checkboxes + deep links (Google 2-Step / youtube.com/earn), and the "what counts" education. Every refresh appends a YppSnapshot (SQLite v10) so the analytics slice has history from day one. - ChannelStatsService (channels.list statistics,auditDetails,contentDetails + playlistItems.list, virtual CaptureCurrentAsync = test seam); YppTrackerViewModel mirrors LiveBroadcastFormViewModel. - MainViewModel.Ypp.cs: Ypp property, ChannelStatsFactoryOverride seam, one-open-at-a-time drawer exclusivity (both directions); account sign-in/out/restore hooks in Account.cs. - PreviewPane.xaml: TextPullOutHost is now a drawer bank [broadcast][ypp][stacked white tabs]; outside-click collapse covers both drawers. MainWindow.xaml.cs updated. - Wait - watchers: the whole feature already reviewed against YouTube's own docs (YPP Earn tab + API availability) before building — see TASKS/task-39-ypp-journey-tracker.md. - ONE integration test: YppPullOutTests (snapshot capture/persist + checklist round-trip + drawer exclusivity). 313/313 pass, 0 warnings. - Memory in same commit: TASKS.md row, ai.md Journey-tracker bullet (roadmap→reality), HANDOFF.md. Slice 2 (Analytics yt-analytics.readonly re-consent → watch-hours/Shorts + velocity/ETA + sparkline) is queued behind this, not merged.
84 lines
5.5 KiB
Markdown
84 lines
5.5 KiB
Markdown
# TASK 39 — YPP journey tracker (slice 1: current-scope data)
|
|
|
|
**Status:** ✅ Slice 1 Done — shipped 2026-09-22 (Good Dog: ONE integration test)
|
|
|
|
## Idea (creator ruling 2026-09-21, feasibility signed off 2026-09-22)
|
|
|
|
Track the connected creator's YouTube Partner Program progress inside the app: a
|
|
**"YPP"** tab on the Live screen's right edge, just below **Stream Settings**, sliding out the
|
|
same way. Everything the creator needs about YPP eligibility and their position ships in-app —
|
|
no leaving the app for definitions or numbers. The user also holds the YPP ruleset (studio
|
|
"Earn" tab): 500 subs / 3 valid public uploads-90d for fan funding; 1,000 subs for full ad /
|
|
Premium revenue; valid public long-form watch hours over trailing 12 months; valid public
|
|
Shorts views (Shorts-feed origin) over 90 days; 0 active community-guidelines strikes; 2FA on;
|
|
linked AdSense; **long-form bar doubles to 8,000h on 2027-02-01** (see ai.md "Journey tracker").
|
|
|
|
**Slice split (user chose slice-1-then-slice-2):** slice 1 uses ONLY the current OAuth scopes
|
|
— no re-consent; slice 2 adds the Analytics API (`yt-analytics.readonly`, one-time Google
|
|
re-consent) behind the reserved `IChannelStatsProvider` seam for watch-hours/Shorts-views +
|
|
velocity/ETA. Slice 1 still writes SQLite snapshots on every refresh, so slice 2 has history
|
|
from day one.
|
|
|
|
## Honest limits (told to the user before building)
|
|
|
|
- No public API exposes the Earn-tab counters or the "valid public" filtering. Numbers are
|
|
**API-derived and labeled close-to-but-not-identical to Studio**.
|
|
- **2FA and AdSense linkage are not readable from ANY YouTube API** → in-app self-reported
|
|
checkboxes with deep links (Google's 2-Step page / the Earn page). Standing IS readable from
|
|
`channels.list auditDetails` (overall + the three sub-flags) — live, no re-consent.
|
|
- `subscriberCount` is rounded to 3 significant figures by Google.
|
|
|
|
## What shipped
|
|
|
|
- `Models/YppThresholds.cs` — thresholds as **versioned date-aware data** (never constants, per
|
|
ai.md): Tier1 (500 / 3,000h / 3M Shorts), `Tier2For(when)` returns 4,000h/10M before
|
|
**2027-02-01** and 8,000h/20M from then; `UploadsRequiredPer90Days = 3`.
|
|
- `Models/YppStatSnapshot.cs` + `Services/LayoutStore.Ypp.cs` + schema **v9 → v10**
|
|
(`YppSnapshot` table): append-only snapshot history; manual checklist (`Ypp.TwoFactorEnabled` /
|
|
`Ypp.AdSenseLinked`); last-refresh-UTC gate (the once-daily gate a quota-thin analytics slice
|
|
will need is baked in now).
|
|
- `Services/ChannelStatsService.cs` — `channels.list?mine=true&part=statistics,auditDetails,
|
|
contentDetails` (subs/views/videos + uploads-playlist id + standing flags) then
|
|
`playlistItems.list` (recent 50) counts uploads in the trailing 90 days. Virtual
|
|
`CaptureCurrentAsync` = the test seam. No new scope.
|
|
- `Services/YppTrackerViewModel.cs` — drawer state + `Toggle/CloseDrawerCommand` + `RefreshCommand`
|
|
(mirrors `LiveBroadcastFormViewModel`), plus: signed-out/loading/error states, tier progress
|
|
bars (subs Tier-1 & Tier-2, uploads), live standing checkbox + detail, manual 2FA/AdSense
|
|
checkboxes, `WhatCountsText` education, deep links, `EnsureLoadedAsync` (once/day gate) +
|
|
explicit refresh, `OnAccountChanged()` hook.
|
|
- `ViewModels/MainViewModel.Ypp.cs` + ctor — `Ypp` property, `ChannelStatsFactoryOverride` test
|
|
seam (mirrors `CameraEnumeratorOverride`), and **one-open-at-a-time exclusivity** wired both
|
|
directions (opening YPP closes Stream Settings and vice-versa). Sign-in/sign-out/restored-session
|
|
hooks in `MainViewModel.Account.cs`.
|
|
- UI (`Controls/PreviewPane.xaml`) — `TextPullOutHost` is now one drawer bank: the broadcast
|
|
drawer, then the YPP drawer, then a **stacked white-tab column** (Stream Settings above YPP).
|
|
Same animated-width pattern; only one open at a time. `MainWindow.xaml.cs` outside-click
|
|
collapse covers both drawers. `IsClickInsideDrawer` (PreviewPane.cs) unchanged — covers the rail.
|
|
- ONE integration test — `ytLive.Tests/YppPullOutTests.cs` (RealApp + temp DB): (1) connected
|
|
VM captures a fake snapshot → progress props, snapshot persisted, checklist round-trip, drawer
|
|
toggles; (2) real MainWindow enforces drawer exclusivity both ways.
|
|
- Slice-2 placeholder rows (long-form hours / Shorts views) render a clear "arrives with the
|
|
extended analytics update" state in the drawer.
|
|
|
|
## Deep links (verified stable endpoints; the exact Studio "Earn" tab path is a slice-2 nicety)
|
|
|
|
- Earn: `https://www.youtube.com/earn`
|
|
- 2FA: `https://myaccount.google.com/signinoptions/two-step-verification`
|
|
- AdSense: `https://support.google.com/youtube/answer/72851`
|
|
|
|
## Slice 2 (outline, queued)
|
|
|
|
`yt-analytics.readonly` (+ re-consent) → `YouTubeAnalyticsService` behind `IChannelStatsProvider`
|
|
(`ai.md`): long-form `estimatedMinutesWatched` (12-mo), Shorts-feed views (90-d), then velocity /
|
|
(de)acceleration / ETA from the `YppSnapshot` history + a sparkline. Own Good Dog landing + task
|
|
doc; the once-daily refresh gate and history table are already in place.
|
|
|
|
## Acceptance (slice 1)
|
|
|
|
- [x] YPP white tab appears below Stream Settings on the Live screen right edge.
|
|
- [x] Clicking it slides out the YPP drawer; opening one drawer closes the other; outside-click
|
|
collapses whichever is open.
|
|
- [x] Signed out → "Sign in to YouTube…" hint. Signed in → subs (Tier 1 & Tier 2), uploads/90d,
|
|
live standing, manual 2FA/AdSense, education text, refresh + last-updated.
|
|
- [x] Every refresh appends a snapshot to SQLite (history ready for slice 2).
|
|
- [x] 0 warnings / 0 errors; **313/313 tests pass** (one new Good Dog integration test). |