feat(ui): TASK 42 top bar redesign — one Record-or-Stream surface (2026-09-22)

The bar now renders ONE surface from the FIRST decision the creator makes —
Record or Stream — with each world being the whole bar. Creator directive:
"I wrote the fucking thing and I still can't figure-out how to do stuff",
"forget this one dog plan bullshit". OBS/streaming-tool reference for the
one-surface paradigm: https://obsproject.com (single mode toggle + context
actions) — cited per spin-guard habit; the go-live/test pattern follows
CEV (https://cev-desktop.aSean.xyz) precedent.

- Mode: single segmented REC|ON-AIR switch (SegmentToggle/SegmentLabel in
  Themes/Controls.xaml); radio-exclusive world selection, one tap to flip.
- Record world: switch + Start Recording, zero YouTube identity.
- Stream world: switch + Go Live + Test + account zone right (Sign In until
  connected, then avatar with Change Account/Logout context menu).
- Test is a child of Stream: procs only stream-armed AND signed-in.
- Running: one reality line "dot word elapsed" (REC/LIVE/TEST; green/red/
  gold) + End; worlds + switch retire.
- Gear moved up from bottom bar, ~3 wordmark letters past the brand, single
  click opens Settings/Bug/Feature/About menu (TASK 40 Unit B shipped early).
- Fix folded in: BeginTestStream now arms OnAirPillOn explicitly (unarmed
  pump booted with zero encoder outputs -> "At least one output is required"
  forced-stop cascade from TASK 41's pipeline).
- VM: world/reality props + RaiseTopBarModes() wired into StreamStatus,
  pill, IsRecording, IsTestStream, IsConnected setters.

Doc: ai.md top-bar model, ViewModels/index.md, Controls/index.md, TASKS.md,
HANDOFF.md, new TASKS/task-42-top-bar-redesign.md; task-40 note.

Tests: TopBarModeTests.cs (new Good Dog), PillRadioTests + TestStreamTests
gates updated. Build 0 warnings; full suite 314/315 — single abort is the
pre-existing env-dependent RealMouseDrag test (Path of Exile 2 running)
This commit is contained in:
2026-09-22 17:57:06 -07:00
parent bb5dcb4ba2
commit e69db4d26c
18 changed files with 538 additions and 236 deletions
+63 -49
View File
@@ -1,63 +1,75 @@
# HANDOFF — 2026-09-22 (TASK 41 Test Stream mode shipped; TASK 40 plan queued)
# HANDOFF — 2026-09-22 (TASK 42 top bar redesign implemented; commit + full-suite rerun owed)
## Branch / Commit State
`main` HEAD = **`4d9188a` — docs(plan): TASK 40 App Settings round — saved plan**. Pushed state
at `197ee81`. **Working tree DIRTY — TASK 41 implementation in progress** (code + docs staged for
a single scope-checked commit; HANDOFF rewrite rides in a final docs commit):
`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):
```
?? HANDOFF.md (this rewrite)
M Models/YouTube.cs
M Services/YouTubeStreamService.cs
M Services/YouTubeChatService.cs
?? Services/TestSessionViewModel.cs
M ViewModels/MainViewModel.cs
M ViewModels/MainViewModel.Ypp.cs
M ViewModels/MainViewModel.Account.cs
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 Controls/TopBar.xaml
M Controls/PreviewPane.xaml
?? ytLive.Tests/TestStreamTests.cs
M TASKS.md
?? TASKS/task-41-test-stream-mode.md
M ai.md
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 HANDOFF.md (this rewrite)
```
Build impact: main + tests build clean, **0 warnings**; **314/314 tests pass** (313 prior + 1 new
Good Dog). A flaky real-camera startup test aborted one earlier full-suite run (hardware-dependent,
unrelated to this change); the clean rerun passes fully.
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 41: Test Stream mode (2026-09-22)
## What shipped — TASK 42: top bar redesign (2026-09-22)
Full record: **`TASKS/task-41-test-stream-mode.md`**. Decisions locked with the creator:
chat-overlay-events-only (no stinger/TTS — stays TASK 3 16/17/20), TEST drawer in the right rail,
hybrid simulation (real text insert + local synthetic events). Key design: since every broadcast is
already private-only (`CreateBroadcast` hardcodes `privacyStatus=private`), "Test" is a **session
variant**: `BeginTestStream` → extracted-shared `StartStreamingSession(alsoRecord:false)` (no
dialog, no "last live" stamp, no recording). `IsTestStream` = gold top bar (`#9c6f1c`) + TEST badge
+ "End Test" button face. `TestSession.BeginTest(broadcastId, liveChatId)` opens the TEST drawer
once the liveChatId resolves. Mock Chat Input = real `liveChat/messages.insert` round-trip;
simulated events inject through the poller's `MessageReceived` seam marked `IsSimulated` (hard API
fact: insert only creates text — non-text events are local-only by design). Three-way drawer
exclusivity (Stream Settings / YPP / TEST) in `MainViewModel.Ypp.cs`.
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**:
**Good Dog** — `ytLive.Tests/TestStreamTests.cs` (one `[Fact]`): coordinator protocol w/ fake HTTP
(insert POST body asserted; simulated shapes verified) + real-window exclusivity/gate. Note: the
coordinator section runs on the test thread directly (pure services, no WPF), the window section via
`RealAppHost.Run` — deviates from YppPullOutTests' all-in-one-Run shape for debuggable stacks.
- **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
- **Manual verification still owed** (needs a real run, Windows): Test button → gold face + badge +
auto-open TEST drawer; send a message → appears on a ChatBox overlay after ~2s; simulated rows
render text/member/SuperChat styled; End Test closes it all out cleanly (transition complete).
- The app may be **RUNNING** — in-place build fails MSB3021/MSB3027 until closed (temp-OutDir
- **Full-suite run today: 314/315** — 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). Close the game before running that test; it is unrelated to TASK 42 and has passed
on clean reruns previously.
- **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.
- The full-suite flake earlier: real-camera startup test under the test host (hardware, pre-existing
behavior; unrelated to TASK 41).
- LayerReorderPersistenceTests.RealMouseDrag flaked once pre-TASK-38 (fullscreen game); unrelated.
- 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).
@@ -65,13 +77,15 @@ coordinator section runs on the test thread directly (pure services, no WPF), th
## Next step
Commit TASK 41 (scope-check the declared list first), then **TASK 40 App Settings round is queued**
(`TASKS/task-40-app-settings-round.md`, units A → B → C → D, one Good Dog each). Unit A's
SharedReadOnly control-write question is the one genuinely uncertain technical point — run its
mitigation ladder, spin-guard research before a third guess. Push only on the user's say-so.
Scope-check the declared list → build (0 warnings) → full test suite rerun → **one commit** for
TASK 42 (docs + code same change). 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).
- 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).