feat(ui): double-click-to-buy (AP-171, user-approved) + #353 toolbar text fixes — authored right-justify and two-line name wrap (Fable)
Some checks are pending
Headless portability / portable-headless (ubuntu-latest) (push) Waiting to run
Headless portability / portable-headless (windows-latest) (push) Waiting to run
Headless portability / linux-graphical (push) Waiting to run
Headless portability / linux-vulkan (push) Waiting to run

Double-clicking a vendor shop item now buys through the Buy button's
exact quantity/price path — retail has NO double-click-to-buy (the
named table sweep's negative evidence stands); the user chose the
addition explicitly and AP-171 records it.

#353 (pre-existing, user-reported): the stack-count entry is AUTHORED
HJustify=2 — right-justified flush against the slider on its own row —
and UiField already supported RightAligned; nobody had honored the
authored value. The name element is AUTHORED two lines tall (H=31,
W=140): long names now word-wrap at the authored pixel width onto a
second centered row via two stacked one-line labels reusing the
existing centered draw path (WrapNameTwoLines: greedy word break, no
hyphenation, second row clips like retail).

Ten SelectedObjectController structure tests updated from
single-label to first-label access. Lesson re-learned the hard way:
the first "green" run used a stale TEST assembly (only the App
project had been rebuilt) — the clean-room caught it, per
feedback_stale_build_artifacts. Full App 4,329/3 and Core 4,381/1
verified green on properly rebuilt assemblies; the one transient
Core Release failure did not reproduce and is noted on #351.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-08 17:42:02 +02:00
parent 1688863366
commit d674b99f56
5 changed files with 113 additions and 28 deletions

View file

@ -152,7 +152,7 @@ public class SelectedObjectControllerTests
Assert.False(healthMeterEl.Visible, "health meter must be Visible=false immediately after Bind");
var textChild = nameEl.Children.OfType<UiText>().SingleOrDefault();
var textChild = nameEl.Children.OfType<UiText>().FirstOrDefault();
Assert.NotNull(textChild);
Assert.True(textChild!.Centered, "name UiText must be Centered");
Assert.True(textChild.ClickThrough, "name UiText must be ClickThrough");
@ -171,7 +171,7 @@ public class SelectedObjectControllerTests
var (layout, nameEl, _, _) = FakeLayout();
new Harness().Bind(layout);
var textChild = nameEl.Children.OfType<UiText>().Single();
var textChild = nameEl.Children.OfType<UiText>().First();
Assert.Empty(textChild.LinesProvider());
}
@ -202,7 +202,7 @@ public class SelectedObjectControllerTests
SelectionChangeReason.CombatTargetDied);
Assert.Equal(Replacement, h.Selection.SelectedObjectId);
var lines = nameEl.Children.OfType<UiText>().Single().LinesProvider();
var lines = nameEl.Children.OfType<UiText>().First().LinesProvider();
Assert.Single(lines);
Assert.Equal("Drudge Prowler", lines[0].Text);
}
@ -233,7 +233,7 @@ public class SelectedObjectControllerTests
Assert.Equal(Guid, h.QueryHealthCalls[0]);
Assert.Equal("ObjectSelected", overlayEl.ActiveState);
var lines = nameEl.Children.OfType<UiText>().Single().LinesProvider();
var lines = nameEl.Children.OfType<UiText>().First().LinesProvider();
Assert.Single(lines);
Assert.Equal(ExpectedName, lines[0].Text);
Assert.Equal(new Vector4(1f, 1f, 1f, 1f), lines[0].Color);
@ -338,7 +338,7 @@ public class SelectedObjectControllerTests
Assert.Equal(17u, h.SplitQuantity.Value);
Assert.Equal(17u, h.SplitQuantity.Maximum);
Assert.Equal(1f, slider.ScalarPosition);
Assert.Equal("17 Healing Kits", nameEl.Children.OfType<UiText>().Single().LinesProvider().Single().Text);
Assert.Equal("17 Healing Kits", nameEl.Children.OfType<UiText>().First().LinesProvider().Single().Text);
slider.SetScalarPosition(0.5f);
slider.ScalarChanged!(0.5f);
@ -405,7 +405,7 @@ public class SelectedObjectControllerTests
Assert.Empty(h.QueryHealthCalls);
Assert.Equal("ObjectSelected", overlayEl.ActiveState);
var lines = nameEl.Children.OfType<UiText>().Single().LinesProvider();
var lines = nameEl.Children.OfType<UiText>().First().LinesProvider();
Assert.Single(lines);
Assert.Equal(ExpectedName, lines[0].Text);
}
@ -432,7 +432,7 @@ public class SelectedObjectControllerTests
Assert.False(healthMeterEl.Visible, "meter must be hidden after deselect");
Assert.Equal("", overlayEl.ActiveState);
Assert.Empty(nameEl.Children.OfType<UiText>().Single().LinesProvider());
Assert.Empty(nameEl.Children.OfType<UiText>().First().LinesProvider());
Assert.Equal(new[] { Guid, 0u }, h.QueryHealthCalls);
}
@ -461,7 +461,7 @@ public class SelectedObjectControllerTests
Assert.Equal("ObjectSelected", overlayEl.ActiveState);
Assert.Equal(new[] { GuidA, 0u }, h.QueryHealthCalls);
var lines = nameEl.Children.OfType<UiText>().Single().LinesProvider();
var lines = nameEl.Children.OfType<UiText>().First().LinesProvider();
Assert.Single(lines);
Assert.Equal("Chest", lines[0].Text);
}
@ -870,7 +870,7 @@ public class SelectedObjectControllerTests
// Retail's "{count} {plural}" toolbar label.
var nameElement = layout.FindElement(SelectedObjectController.NameId);
Assert.NotNull(nameElement);
UiText nameLabel = Assert.Single(nameElement!.Children.OfType<UiText>());
UiText nameLabel = nameElement!.Children.OfType<UiText>().First(); // #353: two stacked line labels now
string renderedName = string.Concat(
nameLabel.LinesProvider().Select(static line => line.Text));
Assert.Equal("100 Lead Scarabs", renderedName);
@ -942,7 +942,7 @@ public class SelectedObjectControllerTests
var nameElement = layout.FindElement(SelectedObjectController.NameId);
Assert.NotNull(nameElement);
UiText nameLabel = Assert.Single(nameElement!.Children.OfType<UiText>());
UiText nameLabel = nameElement!.Children.OfType<UiText>().First(); // #353: two stacked line labels now
string renderedName = string.Concat(
nameLabel.LinesProvider().Select(static line => line.Text));