docs: decisions log, queue trim, HANDOFF rewrite — the map stops being a treadmill

TASKS.md 467 → 66 lines: one line per live item, detail stays in TASKS/
task-*.md. The old catalogue (63 rows of paragraph status prose plus the
historical narration) is preserved verbatim as TASKS/archive.md — a
2026-10-29 snapshot, not the live queue. The "out of product" list and every
retired decision moved to docs/ai/decisions.md with cause of death.

HANDOFF.md 440 → 40 lines, and it is now OVERWRITTEN, not appended: branch,
what's in flight, next action, live landmines, pending creator checks. History
belongs to git log; the previous handoff is reachable at ee3c592:HANDOFF.md.

docs/ai/decisions.md (new) collects what no longer governs: the paid tier and
its pricing, Polar, Store IAP, the Store route and why it died, the deleted
Velopack updater, signing economics, the retired monetization ideas, and the
permanent out-of-product list.

Facts that lived in the old ai.md "Current limitations" section were re-homed
rather than dropped with the router rewrite: OAuth/DPAPI/session behaviour →
docs/ai/youtube-api.md; paths, persistence and the Google project number →
docs/ai/environment.md.

verify.sh caps now cover all four always-read files (AGENTS 210, ai 100,
HANDOFF 40, TASKS 120).

Docs-only unit: scope-check run, no build.
This commit is contained in:
2026-10-29 11:10:51 -07:00
parent c9ac348976
commit 5a102a4a37
9 changed files with 723 additions and 981 deletions
+22
View File
@@ -253,3 +253,25 @@ These are the hard facts behind every decision. Full list in `TASKS.md`.
already cover insert. **Real stinger/TTS alert widgets are DONE (TASK 43, shipped
2026-09-24) as the native alert box** — see the next section; Text source (TASK 3 item 16)
and the RewardEvent persistence (item 20) remain open.
## Auth & session (YouTube OAuth)
- `Helpers/OAuthCredentials.cs` holds the real ClientId/ClientSecret. Auth is complete and the
session **persists via Windows DPAPI** (`Helpers/TokenStore.cs` → `%APPDATA%\ytLlive\ytLlive.auth`,
CurrentUser scope), reloaded best-effort at startup with a proactive refresh of a near-expiry
access token.
- Sign-in / Change Account lives **inside the Start Stream dialog** — a two-state flow, no separate
Connect button. Sign-out is **explicit only**: `StopStream()` does NOT clear the session
("stopping a recording leaves the creator signed in", 2026-08-29).
- `YouTubeAuthService` takes an optional `HttpClient` + `sessionChanged` + `openBrowser` callback
(test seams + save hook; services are constructed in `MainViewModel`).
- **Change Account must never sign out first** (fixed 2026-09-27): `SignOutYouTubeAsync` →
`TokenStore.Clear()` **deletes** `ytLlive.auth`, so cancelling in the Google chooser left the
creator logged out of a working account. It is now just `await SignInAsync()` — success overwrites
the token through the `sessionChanged` hook; cancel/failure leaves the session untouched.
- **The OAuth flow releases its loopback port on retry:** an abandoned browser attempt holds
`localhost:8765` for its 5-minute timeout, so `AuthenticateAsync` cancels and awaits the abandoned
attempt before binding, serialised by a `SemaphoreSlim` (`_authGate`) — without the gate, two
overlapping calls both see a free port and race to bind it.
- Test seam: `YouTubeAuthService.SetSession()` is public — inject a channel with a future
`TokenExpiry` so `EnsureToken()` passes offline, paired with `HttpClient(new StubHandler())`.