ac6e67aff0
startup.log 2026-09-22 (user runs of the new build): ALL THREE session stops
logged 'Broadcast transition(complete) failed (403) invalidTransition → End
close-out: enableAutoStop will finish'. The old ai.md assumption ('invalidTransition
when autoStop already fired') was wrong — the blind complete POST races
YouTube/autoStop marking the broadcast complete (enableAutoStop=true is always
set, tests included), so a blind POST earned only the 403 + log noise. Per the
transition errors table, invalidTransition is a current-status problem and
complete is not gated on streamStatus (only testing/live are):
https://developers.google.com/youtube/v3/live/docs/liveBroadcasts/transition
Fix: EndBroadcastAsync pre-flights liveBroadcasts.list -> status.lifeCycleStatus
and skips the POST when the broadcast is already complete/revoked; an inconclusive
pre-check still posts (old behavior) rather than silently stranding a live
broadcast; still never throws (enableAutoStop finishes every skip).
Good Dog: EndBroadcast test rewritten to the one contract — live -> GET pre-check
+ POST transition (URL shape asserted); already complete -> zero transition POSTs;
inconclusive pre-check + 403 POST -> error string, never a throw. Full suite
315/315 (one-run audio/mouse timing flakes cleared), build 0 warnings.
Recipe recorded in MyMistakes.md; ai.md / Services index / TASK-9 note corrected.
102 lines
6.6 KiB
Markdown
102 lines
6.6 KiB
Markdown
# HANDOFF — 2026-09-22 (TASK 42 committed; health-poll + end-race fixes tracked)
|
|
|
|
## Branch / Commit State
|
|
|
|
`main` HEAD = **`e69db4d`** (TASK 42 top bar) → **`18ab553`** (health-poll fix, committed).
|
|
Pushed state at `197ee81`. **Working tree DIRTY — end-of-stream race fix (one small change):**
|
|
|
|
```
|
|
M Services/YouTubeStreamService.cs (EndBroadcastAsync pre-flight)
|
|
M ytLive.Tests/YouTubeStreamServiceTests.cs (end-close-out test rewritten)
|
|
M ai.md, Services/index.md, TASKS/task-09-live-stream-management.md, MyMistakes.md
|
|
M HANDOFF.md (this rewrite)
|
|
```
|
|
|
|
## Shipped since last handoff
|
|
|
|
- **TASK 42 top bar redesign** `e69db4d` — see below.
|
|
- **Health-poll parse fix** `18ab553` — `GetStreamHealthAsync` read `healthStatus` as
|
|
a string; the real API nests `status.healthStatus = {status, lastUpdateTimeSeconds,
|
|
configurationIssues[]}` (an OBJECT) → every 2026-09-22 session start logged
|
|
`requires an element of type 'String'`. Fixed to read the nested shape, tolerating the
|
|
old flat shape; the report-by-exception health banner is alive again.
|
|
|
|
## In flight — end-of-stream race fix (end close-out, 2026-09-22)
|
|
|
|
`startup.log` showed `Broadcast transition(complete) failed (403) invalidTransition →
|
|
enableAutoStop will finish` on ALL THREE 2026-09-22 stops (17:09 crash + both test
|
|
sessions). NOT the old "autoStop already fired" assumption (ai.md had it wrong): the
|
|
blind `transition(complete)` POST races YouTube/autoStop marking the broadcast complete
|
|
(via `enableAutoStop=true`, always set — tests included). Fix in the dirty tree:
|
|
`EndBroadcastAsync` pre-flights `liveBroadcasts.list→status.lifeCycleStatus` and only
|
|
POSTs complete from `live`/`testing`, skipping silently when already complete
|
|
(`enableAutoStop` finishes every skip); an inconclusive pre-check still posts (old
|
|
behavior, backstopped); still never throws. Cite:
|
|
https://developers.google.com/youtube/v3/live/docs/liveBroadcasts/transition (errors
|
|
table: invalidTransition = current-status problem, and complete is not gated on
|
|
streamStatus — only testing/live are).
|
|
|
|
## 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
|
|
bullshit. Our one plan is replacing the top menu bar with something usable." The old bar's six
|
|
widget grammars (sliding pills, dots, badges, buttons, hidden sign-in context menu, live-window
|
|
status) collapsed into **ONE surface rendered by the FIRST decision — Record or Stream**:
|
|
|
|
- **Mode switch:** one segmented **REC|ON-AIR** toggle (`SegmentToggle`/`SegmentLabel` styles).
|
|
Going back = one tap on the other segment. Runs idle-only; retires while running.
|
|
- **Record world:** switch + **Start Recording** — no YouTube identity at all.
|
|
- **Stream world:** switch + **Go Live** + Test + account zone (Sign In until connected; then the
|
|
avatar, right-click Change Account/Logout). **Test is a child of Stream** — procs only ON-AIR-armed
|
|
AND signed-in (`ShowTestButton`/`CanStartTest`).
|
|
- **Running:** one reality line `● REC|LIVE|TEST 00:12:34` (green/red/gold `RunningDotBrush`) + End.
|
|
- **Gear** moved up from bottom bar, ~3 wordmark letters past the brand, one click = Settings / Bug /
|
|
Feature / About menu (`GearButton_Click`). Bottom-bar gear removed. Task 40 Unit B is DONE by this.
|
|
- **Sign-in = the account-confirm step:** no Google API enumerates machine accounts; OAuth is
|
|
single-account, so Switch Account re-runs the chooser (`login_hint`+`select_account`) — login and
|
|
account-confirmation are one surface. Existing `ChangeAccountCommand`/`LogoutCommand` resurface via
|
|
the avatar.
|
|
- **PRIVATE/TEST chips gone from the bar** (dev-phase forced-private becomes an OPTION at ship).
|
|
|
|
**Good Dog** — `ytLive.Tests/TopBarModeTests.cs` (one `[Fact]`, `RealAppHost.Run`): default record
|
|
world → arm ON-AIR → sign-in/avatar/Test gating flips → one-tap flip back → direct stream status
|
|
changing + running reality values. Also folded in: TASK 41's latent **Test pipeline bug fix** —
|
|
`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.
|
|
|
|
## ⚠️ Around the task
|
|
- **Full-suite run today: 314/315** (first pass) — the one abort is the pre-existing
|
|
`LayerReorderPersistenceTests.RealMouseDrag_OnTheLayerList_PersistsTheReorder`, which injects
|
|
REAL physical mouse input (`SetCursorPos` + `mouse_event`, see its own docstring) and no-ops while
|
|
another window interferes. Cause observed 2026-09-22: **Path of Exile 2 + NVIDIA Overlay were
|
|
running** (fullscreen-game window hides the test window's rows; the drag never lands → reorder
|
|
assert fails). `AudioPipelineTests.Mix_HonorsProviderGains…` also flaked one run (timing under
|
|
load); both cleared on the 315/315 rerun. Close games before running those tests; unrelated to the
|
|
recent changes.
|
|
- **Manual verification owed** (needs a real run on Windows): the app is a new build number when
|
|
next launched — check the new one-surface bar (switch ↔ worlds, gear menu, reality line).
|
|
- The app may be **RUNNING** — an in-place build fails MSB3021/MSB3027 until closed (temp-OutDir
|
|
workaround in ai.md). Close it before the manual pass.
|
|
- Full-suite note: the pre-existing real-camera startup flake did **not** occur in the 315/315 run.
|
|
- Test-env trap (recorded in the TASK): a dev machine's saved OAuth session loads synchronously and
|
|
makes `IsConnected` come back true in the VM ctor — signed-in-world tests must force
|
|
`vm.IsConnected = false/true` explicitly.
|
|
- `subscriberCount` rounded to 3 sig figs (YPP); carried. YPP slice 2 needs re-consent — do NOT
|
|
merge with other units.
|
|
- Polar product `d105dfa1…` still `$99/yr` — must become one-time before launch (carried).
|
|
`MARCOM.md`/`MONETIZATION.md` gitignored — never commit.
|
|
|
|
## Next step
|
|
|
|
Commit this end-of-stream fix (scope-check first, then build 0-warnings + full suite, then ONE
|
|
commit). Push only on the user's say-so. Then **TASK 40 App Settings round** is queued
|
|
(`TASKS/task-40-app-settings-round.md`) — units **A (camera) → C (defaults) → D (accent)** remain
|
|
(B/gear shipped early); Unit A's SharedReadOnly control-write question is the one genuinely
|
|
uncertain technical point — run its mitigation ladder before a third guess.
|
|
|
|
## Critical working rules (unchanged, still binding)
|
|
|
|
- **Good Dog = ONE integration test per change.**
|
|
- **Scope lock:** declare the file list before editing; `./scripts/scope-check.sh` before commit.
|
|
- WSL builds use the Windows dotnet host (`/mnt/c/Program Files/dotnet/dotnet.exe`, quoted paths).
|
|
- One runtime model, no model switching, no compaction summaries (user directive). |