feat(topbar): three fixed slots — every state changes inside its own slot

Creator correction (2026-11-13): "OpenCode literally inserted Live and Private as two extra flat siblings
inside the outer pill bar, shoving Record to the right and making it look like five ungrouped toolbar
items". Correct — completion states were being added to the BAR instead of to a SLOT.

Now the bar is exactly three slots, and the bar's shape never changes:

  [ Build Scenes ]   [ YouTube ]   [ Record ]

  YouTube:  ▶ YouTube -> [Live | Private | ✕] -> [● Start Live | ✕] (amber: next click goes on air)
            -> [■ Stop Live]
  Record:   ◉ Record  -> [● Start Recording | ✕] -> ● REC hh:mm:ss [■ Stop]
  Build:    enabled only while BOTH action slots are Idle; otherwise disabled at 40% with the tooltip
            "Scene editing is locked while streaming or recording"

State lives in YouTubeSlotState / RecordSlotState, with one RaiseSlotStates() announcing every flag so no
caller can forget one. Esc and the ✕ cancel the slot you are in and nothing else.

This retires the BarWorld model from earlier today, along with TopBarActivityTests (superseded by
TopBarSlotTests, which pins all three flows, the lock, and cancel-to-Idle).

679 pass, 0 failed (the host-hang fallback in f4caea0 counted the per-test lines).
Scope justification: TopBarActivityTests.cs is deleted as part of this unit — it tested the model being
replaced; TopBarSlotTests is its successor.
This commit is contained in:
2026-11-13 12:20:11 -08:00
parent ba60f9ab3d
commit 69f08a478e
10 changed files with 373 additions and 273 deletions
+12 -1
View File
@@ -145,7 +145,18 @@ Placement math itself is covered by `WindowPlacementTests` (pure function, no HW
`FormattedText` — **measure, don't guess**) plus the field's padding and border. Guard:
`ytLive.Tests/GeometryFieldWidthTests.cs`
- Resolution tiers: 1080p60@12 (default) → 1080p30@10 → 720p60@6 → 720p30@6 → **Vertical 1080p30@10 and 1080p60@12 (9:16, 1080×1920)** — the `@` is the CBR target the encoder is asked to hold, between YouTube's H.264 recommendation and the practitioner sweet spot (its 14/17/8 recommendations are the max, the tier minimums 5/6/3 the step-down floor). Vertical mirrors its landscape pair: same pixel count, same rate. The old flat 8 Mbps was an inherited round number, not a measurement. The composition master frame is **always 1920×1080** — a tier is an output rect + target resolution over that master, so source geometry is never rewritten (no rounding drift). 16:9 tiers use the full frame; vertical tiers use a centered **607×1080** window and the preview dims the cropped side strips at 55% black with an accent outline (semi-crop — the cut area stays visible). A resolution badge in the preview corner shows the active tier. The resolution picker and live readouts (bitrate/FPS/dropped/duration) are in the top-left cell above the layers panel; the bottom bar is an empty height-preserving spacer. Under the picker sits the creator's own **rate box** (Mbps, per tier, persisted as `Video.Rate.<tier>`): the tier chooses geometry, the box chooses bits, clamped at the tier's YouTube minimum and otherwise unbounded because live step-down is the thing that catches a wrong guess. Its tooltip carries YouTube's recommendation for the tier (`Models/RateLadder`). Every consumer — encoder options, step-down policy, clip buffer — reads `MainViewModel.EffectiveQuality`, never the raw tier. A **tooltip** explains finding upload bandwidth — an in-app speed test was deliberately dropped (unreliable). The future encoder crops the master to the rect and scales to the tier's Width×Height
- **The top bar has ONE decision (creator ruling 2026-11-12): `[ Build Scenes ][ Record / Go Live ]`.**
- **The top bar is THREE FIXED SLOTS (creator ruling 2026-11-13): `[Build Scenes] [YouTube] [Record]`.**
The bar's shape never changes — every transition happens INSIDE one slot, so neighbouring buttons cannot
move. Attempts that added completion states as siblings (`Live`, `Private`, `Stop`) read as five
ungrouped toolbar items and pushed Record around; that is the mistake this replaces.
Slot flows (`MainViewModel.TopBar.cs`, `YouTubeSlotState` / `RecordSlotState`):
- **YouTube:** Idle → ChoosePrivacy (`Live | Private | ✕`) → Armed (`Start Live|Start Private | ✕`, amber
border because the next click goes on air) → Streaming (`Stop Live|Stop Private`).
- **Record:** Idle → Armed (`Start Recording | ✕`) → Recording (`● REC hh:mm:ss` + `Stop`).
- **Build Scenes** is enabled only while BOTH action slots are Idle; otherwise it is disabled at 40%
opacity with the tooltip "Scene editing is locked while streaming or recording".
Arming and starting stay separate presses: Start is the last "are you ready?" moment.
- **Legacy note — superseded by the three-slot bar above:**
Build Scenes ships highlighted and **is** edit mode — the standalone EDIT toggle is gone, so "which
world am I in?" and "can I edit?" cannot disagree (`MainViewModel.TopBar.cs`, `BarWorld`). The segment
leads the centre cluster and stays present in every state; clicking the highlighted half re-enters it,