feat(topbar): the segmented mode switcher, to the mock

Implements the creator's mock (2026-11-12) for the world segment: pill track (#1E222B, radius 12,
padding 4) holding two equal halves; the active half sits on a lighter pill (#2E3440, radius 8) with
#E2E8F0 bold text and a coral (#F26D6D) icon; the inactive half is #8892A4 on a one-alpha black face so
the whole area hit-tests; hover tints #252A34 on the inactive half only, so the active one never looks
clickable-but-dim. Labels are "Build scenes" / "Record / go live".

Two deliberate deviations from the pasted recipe, both to honour this repo:
- State is the existing BarWorld binding plus a command, not a code-behind SetActiveMode — one source of
  truth, and the change still raises the same property-changed notification a mode event would.
- Styles live in Themes/Controls.xaml (SegmentHalf, SegmentIcon) with colours as tokens in Tokens.xaml,
  rather than inline properties on the elements: AGENTS requires shared styles, and one DataTrigger on
  each element's own Tag serves both halves.

DesignTokensTests caught the one raw literal (#01000000) and it is now Brush.SegmentHitArea, which is the
rule working as intended.

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 16:20:25 -08:00
parent b92569a8c7
commit 6b4aa99863
4 changed files with 86 additions and 25 deletions
+7 -3
View File
@@ -29,8 +29,8 @@ public class UiShellTests
// 2. The world segment replaced the EDIT toggle (creator ruling 2026-11-12): both halves are
// present, each lit by its own world flag through the shared style.
Assert.Contains("Text=\"Build Scenes\"", topBar);
Assert.Contains("Text=\"Record / Go Live\"", topBar);
Assert.Contains("Text=\"Build scenes\"", topBar);
Assert.Contains("Text=\"Record / go live\"", topBar);
Assert.Contains("Tag=\"{Binding IsBuildWorld}\"", topBar);
Assert.Contains("Tag=\"{Binding IsOutputWorld}\"", topBar);
Assert.DoesNotContain("x:Name=\"EditBody\"", topBar);
@@ -38,6 +38,10 @@ public class UiShellTests
// 2b. The segment is Build-world only: choosing Record / Go Live REPLACES it with the output
// line (creator ruling 2026-11-12), and a chevron is the way back.
Assert.Contains("Data=\"M3,4h8v6H3V4zM13,4h8v6h-8V4zM3,12h8v8H3V12zM13,12h8v8h-8V12z\"", topBar);
// Segmented switcher (creator mock 2026-11-12): pill track, two equal halves, coral active icon.
Assert.Contains("Brush.SegmentTrack", topBar);
Assert.Contains("Style=\"{StaticResource SegmentHalf}\"", topBar);
Assert.Contains("Style=\"{StaticResource SegmentIcon}\"", topBar);
Assert.Contains("Visibility=\"{Binding IsBuildWorld, Converter={StaticResource BoolToVis}}\"", topBar);
Assert.Contains("Text=\"Output:\"", topBar);
Assert.Contains("Text=\"press <esc> to return\"", topBar);
@@ -116,7 +120,7 @@ public class UiShellTests
// The world segment leads the centre cluster; the brand column carries no edit control.
var centre = topBar.IndexOf("<StackPanel Grid.Column=\"1\"", System.StringComparison.Ordinal);
var segment = topBar.IndexOf("Text=\"Build Scenes\"", System.StringComparison.Ordinal);
var segment = topBar.IndexOf("Text=\"Build scenes\"", System.StringComparison.Ordinal);
Assert.True(centre > 0 && segment > centre,
"the [ Build Scenes ][ Record / Go Live ] segment must lead the centre cluster");
Assert.DoesNotContain("x:Name=\"EditBody\"", topBar);