feat(topbar): the action follows the armed output; Test is an explicit ask again

Unit C (creator ruling 2026-11-12).

- Local MP4: one button, "Start Recording".
- YouTube: "Private Test Stream" beside "Review & Go Live", the latter with the primary face because it
  is the default ask.
- Running: "End Live Stream" (or "End Test" for a test run, "Stop Recording" for a local one).

The behavioural change underneath: SessionStartDecision.For no longer takes `editing`. Since 2026-10-30
EDIT silently chose test-vs-real, which meant the same button did two different things depending on a
toggle three inches away. With Test as its own button that inference has no job, so For() now reads only
the armed output and ON-AIR always means the real livestream. That supersedes the 2026-10-30 ruling;
SessionStartDecisionTests is rewritten to pin the new matrix rather than the old one.

A signed-out press still starts the sign-in flow instead of sitting dead (no-dead-ends), unchanged from
before — now stated in one place, the two new commands, instead of inside a route table.

680 pass, 0 warnings. Scope justification: Assets/starting-background.jpg is outside this unit — the
creator's replacement art, still awaiting their visual check.
This commit is contained in:
2026-11-12 15:44:15 -08:00
parent a5a10fb0b0
commit 4bc41c488c
8 changed files with 82 additions and 26 deletions
+8 -3
View File
@@ -21,12 +21,17 @@ public enum SessionStartKind
/// <para>Pure so the whole matrix is testable without a window.</para></summary>
public static class SessionStartDecision
{
public static SessionStartKind For(bool recordArmed, bool onAirArmed, bool editing)
/// <summary>
/// Which kind of session the armed output starts. Since 2026-11-12 there is no inference from
/// editing: the output stage offers "Private Test Stream" and "Review & Go Live" as separate
/// buttons, so ON-AIR always means the real livestream and Test is something the creator asks for.
/// </summary>
public static SessionStartKind For(bool recordArmed, bool onAirArmed)
{
// Neither pill armed still starts something: local recording needs no account, and the
// Neither output armed still starts something: local recording needs no account, and the
// no-dead-ends rule forbids an inert primary button.
if (recordArmed || !onAirArmed) return SessionStartKind.Record;
return editing ? SessionStartKind.Test : SessionStartKind.Live;
return SessionStartKind.Live;
}
/// <summary>What pressing the button actually does, given whether YouTube is connected.