feat(topbar): the recording lives in the pill — green pulse, REC clock, Stop Recording

Build 730 (creator):

- Left pill while recording: solid #2ECC71 dot pulsing 1.0 <-> 0.2 on the existing one-second timer, and
  "REC: hh:mm:ss" in Consolas so the digits cannot jitter the pill's width. Idle face unchanged.
- Right pill while recording: a stop square and "Stop Recording"; clicking it runs the normal stop, which
  halts and resets the clock and returns the pair to "Local MP4" / "Start Recording".
- The separate reality line (dot + REC/LIVE + elapsed + End) is now LIVE-ONLY: a recording would otherwise
  print the same three facts twice. RunningVisible is IsLive, and ShowOutputStage no longer hides itself
  for a recording (it must be on screen to carry the clock and the stop).

Two rules collided and are now separated rather than merged: a creator's Stop on a recording restores the
Local MP4 pair (build 730), while a pipeline FAILURE still clears both pills (the 2026-09-01 zombie rule).
StopStream(afterFailure:) carries the difference, and the pump-failure path passes it.

RealAppHost.Run was rethrowing with `throw failure;`, which destroyed the original stack — a failing
assertion inside the action reported the wrapper's line instead of its own. ExceptionDispatchInfo now
preserves it; that is how the two failures above were located in one pass instead of five.

680 pass (one audio-pipe instance flake under full-suite load, passes alone), 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 16:52:36 -08:00
parent caf5d6130d
commit 70fcf89f9e
9 changed files with 89 additions and 26 deletions
+3 -1
View File
@@ -64,8 +64,10 @@ public sealed class RealAppHost : IDisposable
failure = ex;
}
});
// Rethrow preserving the ORIGINAL stack: `throw failure;` reset it to this line, which hid
// which assertion actually failed inside the passed action.
if (failure != null)
throw failure;
System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(failure).Throw();
}
/// <summary>Awaits <paramref name="action"/> on the App's STA thread and rethrows
+13 -7
View File
@@ -84,16 +84,22 @@ public sealed class TopBarModeTests
Assert.False(vm.ShowTestButton);
Assert.Equal("Start Recording", vm.PrimaryStartButtonLabel);
// Running retires the switch and worlds; the reality line pins the mode.
vm.RecordPillOn = false;
vm.OnAirPillOn = true;
// A local recording STAYS in the output world (build 730): the pill carries REC, the
// clock and the stop, so the separate reality line stays away — it would be a second set
// of the same facts.
vm.EnterOutputWorldCommand.Execute(null);
vm.RecordPillOn = true;
vm.OnAirPillOn = false;
vm.IsRecording = true;
Assert.False(vm.ShowIdleWorld);
Assert.True(vm.RunningVisible);
Assert.Equal("REC", vm.RunningText);
Assert.Equal("#22c55e", vm.RunningDotBrush);
Assert.True(vm.ShowOutputStage);
Assert.False(vm.RunningVisible);
Assert.StartsWith("REC: ", vm.RecordChipText);
vm.IsRecording = false;
// A live stream retires the stage instead; the reality line pins the mode.
vm.StreamStatus = StreamStatus.Streaming;
Assert.True(vm.RunningVisible);
Assert.False(vm.ShowOutputStage);
Assert.Equal("LIVE", vm.RunningText);
Assert.Equal("#ff5451", vm.RunningDotBrush);
vm.IsTestStream = true;
+13
View File
@@ -63,6 +63,19 @@ public class TopBarWorldTests
Assert.True(vm.LocalMp4ChipVisible);
Assert.True(vm.ShowStartRecordingAction);
Assert.False(vm.ShowEndRecordingAction);
Assert.Equal("Local MP4", vm.RecordChipText);
// Recording: the pill carries REC + the clock, the stage stays up, and the live reality
// line does not (its dot/elapsed/stop would be a second set of the same facts).
vm.IsRecording = true;
Assert.True(vm.ShowOutputStage);
Assert.False(vm.RunningVisible);
Assert.False(vm.ShowStartRecordingAction);
Assert.True(vm.ShowEndRecordingAction);
Assert.StartsWith("REC: ", vm.RecordChipText);
vm.IsRecording = false;
Assert.Equal("Local MP4", vm.RecordChipText);
// Neither action exists while building scenes.
vm.EnterBuildWorldCommand.Execute(null);