Closing with a recording active called Shutdown() straight away, which disposed the frame pump and the
audio mixer underneath a running loop: ffmpeg never flushed, the file was never finalized or renamed,
and the pump was torn down mid-iteration. That is the 'app goes autistic on close' report.
MainWindow_Closing now holds the window open, runs StopBeforeShutdownAsync (the ordinary stop, bounded at
~6 s so a stuck encoder cannot make the app unclosable), then closes for real. StopStream already honours
the audio-before-video ordering, so the close path inherits it instead of inventing its own teardown.
Test: with nothing running, closing must not hold the window at all (the fast path stays fast).
Scope justification: Assets/starting-background.jpg is outside this unit — the creator's replacement art,
still awaiting their visual check.
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.
Build 729 (creator):
- "press <esc> to return" loses the word "press"; the label is "Record / Go-Live".
- The bar takes focus on "Build scenes" and Tab cycles between the two halves only
(KeyboardNavigation.TabNavigation="Cycle"), never off into the window.
- Entering Record / Go-Live starts with NOTHING chosen — the output switches are a fresh decision,
not a remembered one.
- Three states: nothing chosen [YouTube][Local MP4]; YouTube chosen [YouTube(red)][Live Stream][Private
Stream]; Local MP4 chosen [Local MP4(red)][Start Recording] → [End Recording] while recording. Only the
chosen output is red; the action chips never highlight. Choosing Local MP4 takes the left slot and the
YouTube chip disappears with it, which is what "slides to the left, replacing it" describes.
"Private Stream" is the private-visibility stream — the same thing this app has always called the test
stream (creator's clarification, so the wording is deliberate, not a new broadcast type).
Output chips are one style with a Tag-driven trigger (OutputChip + OutputChipIcon), so a chip's red face
and its glyph colour can't drift apart; the one-alpha hit-area colour is a token, because
DesignTokensTests rightly refuses literals in theme files.
680 pass, 0 warnings. Scope justification: Assets/starting-background.jpg is outside this unit — the
creator's replacement art, still awaiting their visual check.
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.
Unit B (creator ruling 2026-11-12). Pressing Record / Go Live now changes the bar: the REC/ON-AIR pill
pair becomes two labelled switches with glyphs (the YouTube mark, the record dot), under an "Output"
label and beside "One output at a time". Build world shows nothing but its own segment.
Two things this exposed that were not cosmetic:
- **Local MP4 was not actually the default.** Both pills started unarmed, so the stage opened with no
output selected; the old screen got away with it because the primary button inferred "record" from
"nothing armed". RecordPillOn now starts true, which is what "pos = ON (default)" means in code.
- **The stage gates the whole idle cluster, not just the switch.** Gating only the switch left the start
button visible in the Build world — a Start with no output chosen. ShowOutputStage (output world, idle,
not recording) wraps the cluster, so a running session hides the stage too: its output cannot change.
Exclusivity stays in the view model (arming one drops the other); the XAML merely reflects it.
TopBarWorldTests pins the stage's visibility and the radio pair starting at Local MP4.
680 pass, 0 warnings. Next: unit C (output-dependent primary action, End labels).
Scope justification: Assets/starting-background.jpg is outside this unit — the creator's replacement art,
still awaiting their visual check.
Unit A of the top-bar redesign (creator ruling 2026-11-12). The standalone EDIT toggle is gone;
Build Scenes IS edit mode, so "which world am I in?" and "can I edit?" cannot disagree any more.
BarWorld (ViewModels/MainViewModel.TopBar.cs) has two values, Build and Output. Build ships
highlighted at launch and sets EditingActive on; choosing Record / Go Live turns it off. Clicking the
already-highlighted half is deliberately not a no-op: it re-enters that world, which is the only way
back to the layers during a session now that the EDIT button is gone (unit 1 made that legal).
WorldSegmentButton (Themes/Controls.xaml) is a Button, not a ToggleButton, and each half passes its
own flag through Tag to one shared DataTrigger — a ToggleButton would fight the view model and, with a
one-way IsChecked, clear its own binding on first click.
UiShellTests updated where it guarded the old EDIT markup (it now guards the segment). TopBarWorldTests
pins launch state, both transitions, and the re-enter case.
679 pass, 0 warnings. Next: unit B (output switch), unit C (output-dependent action + End labels).
Scope justification: Assets/starting-background.jpg is outside this unit — the creator's replacement
art, still awaiting their visual check.