Account zone is world-independent: avatar stays in Record world

Creator on the live build: 'why do you make the youtube user icon go away?'
The TASK 42 one-surface bar had scoped the account zone to Stream only
('Record world has no YouTube identity at all'), so arming REC dropped the
avatar and the bar height bounced with it ('really fucking annoying').

Fix: ShowSignInButton / ShowAvatarButton are now plain !IsConnected /
IsConnected — app-level identity, present in both worlds, so the right
column never pops in/out and the bar height stays pinned. Test remains a
child of Stream (still stream-armed AND signed-in).

TopBarModeTests updated to the new contract: signed-out Record world shows
Sign In; flipping back to REC keeps the avatar. PillRadioTests untouched
(it only exercises the ON-AIR face). Docs corrected in-place (ai.md state
model, TASK 42 scope). Gate: clean build 0-warnings, 316/316, scope passed.
This commit is contained in:
2026-09-23 16:53:25 -07:00
parent cb750660c3
commit 3a4e17c735
5 changed files with 53 additions and 42 deletions
+21 -20
View File
@@ -7,19 +7,26 @@
Pushed state at `197ee81` (36 commits ahead locally — push only at a sub-milestone / on the
user's say-so).
**Working tree DIRTY — two finished hotfix work units from the live build #77 session:**
**Working tree DIRTY — account zone made world-independent (creator 2026-09-23):**
```
M Controls/PreviewPane.xaml (audio-sync status dot removed + YPP standing row)
M Themes/Controls.xaml (IntToSyncBrush converter resource removed)
D Helpers/IntToSyncBrushConverter.cs (converter dead — deleted)
M Services/ChannelStatsService.cs (YPP auditDetails part dropped; 403 body logged)
M Services/YppTrackerViewModel.cs (standing flags removed; honest deep-link text)
M ytLive.Tests/YppPullOutTests.cs (fixture loses the standing flags)
?? ytLive.Tests/ChannelStatsServiceTests.cs (NEW Good Dog: no auditDetails in URL + parses)
M ai.md, TASKS/task-22, TASKS/task-39, TASKS.md, MyMistakes.md, HANDOFF.md
M ViewModels/MainViewModel.Streaming.cs (ShowSignInButton = !IsConnected, ShowAvatarButton = IsConnected)
M ytLive.Tests/TopBarModeTests.cs (REC-world asserts: avatar stays, Sign In visible signed-out)
M ai.md, TASKS/task-42, HANDOFF.md
```
Previous two hotfix units (audio-sync dot + YPP 403/statistics-string) are COMMITTED (`cb75066`);
top-bar feedback COMMITTED (`d72949e`).
## ⚠️ The account-zone unit (this turn)
Creator, clicking RECORD on the live build: "why do you make the youtube user icon go away?" — the
TASK 42 one-surface bar had scoped the account zone to the Stream world ("Record world has no YouTube
identity at all"), so arming REC dropped the avatar AND the bar height bounced with it ("really fucking
annoying"). Fix: `ShowSignInButton`/`ShowAvatarButton` are now world-independent (`!IsConnected` /
`IsConnected`) — the right column never pops in/out, bar height pinned. Test stays a child of Stream.
`TopBarModeTests` updated: signed-out Record world shows Sign In; flipping back to REC keeps the avatar.
## ⚠️ The app is RUNNING, and that's the story of this round
Ac6e67a **could not be verified through the normal gate** because the user was *looking at the
@@ -83,17 +90,11 @@ the copy stage with XAML/markup compiling clean (only the 2 copy errors → code
## Next step
1. **Close the running app (PID 17752 holds the exe), then run the full gate** —
`./scripts/verify.sh` with the full declared scope (audio-dot unit + YPP fix): clean build
0-warnings + full suite + scope check. Then commit BOTH units — they share PreviewPane.xaml /
ai.md, so a single hotfix commit ("build-77 polish + YPP refresh 403") is the honest shape, or
two commits if the user prefers strict splits.
⚠️ The YPP Good Dog (`ChannelStatsServiceTests`) and the updated `YppPullOutTests` cannot even
BUILD until the app closes (the app csproj copy fails on the locked exe) — they were written but
not yet executed.
2. After the gate: push decisions pending (user's call). TASK 40 Unit A next.
1. **Run the gate on the account-zone unit** (declared scope: `ViewModels/MainViewModel.Streaming.cs`,
`ytLive.Tests/TopBarModeTests.cs`, `ai.md`, `TASKS/task-42-top-bar-redesign.md`, `HANDOFF.md`) —
`./scripts/verify.sh`: clean build 0-warnings + full suite + scope check. Then commit it
("account zone is world-independent — avatar stays in Record world", code + docs in one).
Repo is 36 commits ahead of pushed `197ee81`; push decision is the creator's (sub-milestone).
## Critical working rules (unchanged, still binding)
+4 -1
View File
@@ -21,7 +21,10 @@ world is the whole bar; the other world never exists on screen.
- **Mode switch:** one segmented **REC|ON-AIR** toggle (former two pills are a
binary, radio-exclusive decision). Going back = one tap on the other segment.
Idle-only; while running the switch retires.
- **Record world:** `switch + Start Recording`. **No YouTube identity at all.**
- **Record world:** `switch + Start Recording`. **No Test — stream actions don't
exist here.** The account zone IS present (2026-09-23 correction, creator
"why do you make the youtube user icon go away?"): the avatar / Sign In are
**world-independent**, keeping the right column — and the bar height — pinned.
- **Stream world:** `switch + Go Live + Test` and the **account zone** on the right
(Sign In while signed-out — the pending sequence step rendered exactly when
needed; the avatar once connected, right-click = Change Account/Logout).
+10 -9
View File
@@ -195,21 +195,22 @@ public partial class MainViewModel : ViewModelBase
public bool RunningVisible => IsLive || IsRecording;
/// <summary>Record world: the switch sits on REC and Go says "Start Recording".
/// No account zone, no Test — YouTube doesn't exist here.</summary>
/// No Test — YouTube's stream actions don't exist here. The account zone IS
/// here: it's app-level identity, not a stream-world widget (creator 2026-09-23).</summary>
public bool ShowRecordAction => ShowIdleWorld && !OnAirPillOn;
/// <summary>Stream world: the switch sits on ON-AIR; Go says "Go Live" and Test
/// appears. The account zone belongs to this world because login is a step in
/// THIS sequence.</summary>
/// appears.</summary>
public bool ShowStreamActions => ShowIdleWorld && OnAirPillOn;
/// <summary>Sign In appears on the right only inside the Stream world, signed-out
/// — the pending step of the stream sequence, made visible exactly when it's next.</summary>
public bool ShowSignInButton => ShowStreamActions && !IsConnected;
/// <summary>Account zone is WORLD-INDEPENDENT (creator 2026-09-23): Sign In /
/// avatar stay put in Record and Stream worlds alike, so the right column never
/// pops in and out — which pins the bar height (no more bounce between worlds).</summary>
public bool ShowSignInButton => !IsConnected;
/// <summary>The avatar confirms WHICH account is about to stream — only in the
/// Stream world, signed-in. Right-click for Change Account / Logout.</summary>
public bool ShowAvatarButton => ShowStreamActions && IsConnected;
/// <summary>The avatar — right-click Change Account / Logout. Always visible when
/// signed in, Record or Stream.</summary>
public bool ShowAvatarButton => IsConnected;
/// <summary>The reality word: TEST while a test broadcast runs, LIVE while on the
/// air, REC during a local-record session.</summary>
+6 -3
View File
@@ -639,10 +639,13 @@ lights when a session is actually running.
`RaiseTopBarModes()`). The old sign-lights + two pill toggles + status dot + PRIVATE/TEST chips are
gone; the mode is one **segmented REC|ON-AIR switch** (`SegmentToggle`/`SegmentLabel` styles), and
"going back" is one tap on the other segment. Idle worlds: Record = switch + "Start Recording"
(`PrimaryStartButtonLabel` names the armed world) and **no YouTube identity at all**; Stream = switch
(`PrimaryStartButtonLabel` names the armed world); Stream = switch
+ "Go Live" + Test (Test is a **child of Stream** — visible only stream-armed AND signed-in) plus the
account zone on the right (`ShowSignInButton` = stream world signed-out; `ShowAvatarButton` = the
avatar, right-click Change Account/Logout). Running retires the switch and worlds; one reality line
account zone on the right. **The account zone is WORLD-INDEPENDENT (2026-09-23):**
`ShowSignInButton` = signed-out and `ShowAvatarButton` = the signed-in avatar, in Record and Stream
worlds alike — it's app-level identity, and keeping it put pins the bar height (it no longer pops in
and out between worlds). Right-click the avatar = Change Account/Logout. Running retires the switch
and worlds; one reality line
`● word elapsed` + End replace them (`RunningVisible`/`RunningText`/`RunningDotBrush` — TEST/LIVE/REC,
gold/red/green). The account **status dot is gone** — the avatar/Sign In pair carries connectedness.
`ShowPrimaryStartButton` remains the always-idle face. `StopStream` clears both pills (see "Stop ends
+12 -9
View File
@@ -10,11 +10,12 @@ namespace ytLive.Tests;
/// <summary>
/// Top bar redesign — the bar is ONE surface rendered by the FIRST decision
/// (Record or Stream). Each world is the whole bar: the Record world has no
/// YouTube identity at all, the Stream world owns the account zone (Sign In or
/// avatar) plus Test. The switch is a two-position gate, so "going back" is one
/// tap on the other segment. While running, the switch retires and a single
/// reality line (dot + word + elapsed) + End replace it.
/// (Record or Stream). Each world is the whole bar: Test is a child of Stream,
/// but the account zone (Sign In / avatar) is WORLD-INDEPENDENT (creator
/// 2026-09-23) so the right column never pops in and out and the bar height
/// stays pinned. The switch is a two-position gate, so "going back" is one tap
/// on the other segment. While running, the switch retires and a single reality
/// line (dot + word + elapsed) + End replace it.
/// </summary>
[Collection("RealApp")]
public sealed class TopBarModeTests
@@ -45,11 +46,12 @@ public sealed class TopBarModeTests
vm.IsConnected = false;
// Default (nothing armed) = the Record world: Go says "Start Recording",
// no account zone, no Test — YouTube doesn't exist in this world.
// no Test — but the account zone is app-level and stays (signed out →
// Sign In visible even here; no avatar).
Assert.True(vm.ShowIdleWorld);
Assert.True(vm.ShowRecordAction);
Assert.False(vm.ShowStreamActions);
Assert.False(vm.ShowSignInButton);
Assert.True(vm.ShowSignInButton);
Assert.False(vm.ShowAvatarButton);
Assert.False(vm.ShowTestButton);
Assert.Equal("Start Recording", vm.PrimaryStartButtonLabel);
@@ -72,11 +74,12 @@ public sealed class TopBarModeTests
Assert.True(vm.ShowAvatarButton);
Assert.True(vm.ShowTestButton);
// Going back is one tap: flipping REC returns the bar to the Record world.
// Going back is one tap: flipping REC returns the bar to the Record world —
// the avatar STAYS (world-independent account zone), only Test retires.
vm.RecordPillOn = true;
Assert.False(vm.ShowStreamActions);
Assert.True(vm.ShowRecordAction);
Assert.False(vm.ShowAvatarButton);
Assert.True(vm.ShowAvatarButton);
Assert.False(vm.ShowTestButton);
Assert.Equal("Start Recording", vm.PrimaryStartButtonLabel);