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
+21 -13
View File
@@ -65,13 +65,13 @@ public sealed class PillRadioTests
/// <summary>Good Dog (one integration test, creator ruling 2026-09-20): the ON-AIR
/// pill is armable signed-out — intent may light while the account is offline (a
/// greyed pill was the dead end the guard shipped to forbid). A front-branch on
/// the ON-AIR setter that dropped the pill headlessly is what the pill is FOR. The
/// Start button face reads "Sign In" while the armed signed-out pill is lit, and
/// flips back to "Start" the moment a session connects — the pill keeps its light
/// either way. Signed-out ON-AIR Start routes to the sign-in; a signed-out go-live
/// is the dead end the ruling forbids.</summary>
/// the ON-AIR setter that dropped the pill headlessly is what the pill is FOR.
/// Since the top bar redesign, the Go face names the armed WORLD ("Go Live")
/// rather than the account race — a signed-out Stream press routes into the
/// sign-in inside StartSession, and the dedicated Sign In button appears on the
/// right exactly when the stream sequence needs the account step.</summary>
[Fact]
public void SignedOut_OnAir_Pill_Stays_Lit_And_Start_Face_Is_SignIn()
public void SignedOut_OnAir_Pill_Stays_Lit_And_Stream_World_Is_GoLive()
{
_app.Run(RunSignedOutOnAir);
}
@@ -86,6 +86,9 @@ public sealed class PillRadioTests
SqliteConnection.ClearAllPools();
var vm = new MainViewModel();
// The dev machine may hold a real saved OAuth session (loads synchronously
// during the ctor), so the signed-out face is forced deterministically.
vm.IsConnected = false;
// No sign-in: the ON-AIR pill MUST still be able to light — intent before
// reality, armable signed-out (creator ruling 2026-09-20).
@@ -93,16 +96,21 @@ public sealed class PillRadioTests
Assert.True(vm.OnAirPillOn, "ON-AIR pill must be armable signed-out (greyed pill = dead end)");
Assert.True(vm.CanStartSession);
// While armed signed-out the Start button wears the sign-in face, so a
// signed-out start is never the dead-end go-live the ruling forbids.
Assert.Equal("Sign In", vm.PrimaryStartButtonLabel);
Assert.False(vm.ShowPrimaryStartButton && vm.CanStartSession && false,
"sanity: face follows the pill, not the account race");
// While armed signed-out the Go face names the Stream world ("Go Live");
// the account step surfaces as the Sign In button on the right instead,
// and a signed-out start routes into the sign-in (never a dead-end go-live).
Assert.Equal("Go Live", vm.PrimaryStartButtonLabel);
Assert.True(vm.ShowSignInButton);
Assert.False(vm.ShowRecordAction);
Assert.True(vm.ShowStreamActions);
// A session connects: the pill KEEPS its light and the face flips to Start.
// A session connects: the pill KEEPS its light; the stream world's Sign In
// retires and the avatar confirms the account.
vm.IsConnected = true;
Assert.True(vm.OnAirPillOn, "the armed signed-out pill must survive the sign-in");
Assert.Equal("Start", vm.PrimaryStartButtonLabel);
Assert.Equal("Go Live", vm.PrimaryStartButtonLabel);
Assert.False(vm.ShowSignInButton);
Assert.True(vm.ShowAvatarButton);
}
finally
{