edit boxes take focus the way 1.12 does, and the stable shows its pets

- the caret was a wedge in the line: it sat in flow, four units wide, so focusing a login box shoved its text right by one caret and a caret parked mid-word opened a gap; it is a zero-width seam now with the bar drawn as an overlay (a 0×26 seam and a 5×26 bar at the same x, off a node probe), the way the reference anchors its caret quad over the FontString; a blurred box collapses its selection so only the box holding the keyboard shows one; and a dialog answers only keys pressed while it was already on screen — ENTER on an empty form used to open the error dialog and close it in the same frame
- `autoFocus` defaults ON in the reference (the ctor leaves flags = 1, and every one of the ten `autoFocus` attributes in the shipped chain is an opt-out), showing an autoFocus box focuses it after its `OnShow`, and hiding the focused box releases the keyboard — the "never focuses on show" note we had transcribed came from a `call`-only census that missed a tail `jmp`; the loader's flag loop could only ever turn a flag on, so `autoFocus="false"` was a no-op, fixed before the default flipped or it would have grabbed exactly the ten boxes the reference exempts; and the EditBox does fire generic `OnChar` with the spliced string, on every typed or pasted character and never on `SetText`
- the stable master's portrait was a black disc (its session was missing from the `"npc"` token chain — the third window to ship that way, so the test is now exhaustive over every `impl NpcSession` and names the one you forgot) and its model pane draws the selected pet: a stabled pet is a database row with no world entity, so a `PortraitStandIn(display_id)` — an entity that exists only to be mirrored — feeds the existing booth code unchanged; plus the registrar's two service rows were hidden by an inherited attribute, a charter's tooltip carries the guild name and master on line 3, and the Help window drops three strings pointing at a company that stopped answering

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Sam
2026-08-29 17:22:27 +07:00
co-authored by Claude Fable 5
parent dbf9d8057b
commit 0cd0a28942
42 changed files with 1799 additions and 131 deletions
@@ -812,6 +812,7 @@ fn shipped_bag_frame_drives_end_to_end() {
slots.insert(
1,
ContainerSlot {
petition: None,
already_bound: false,
bar_placeable: true,
durability: None,
@@ -435,6 +435,7 @@ fn bag_tooltip_hangs_left_when_the_slot_sits_in_the_right_half() {
slots.insert(
1,
ContainerSlot {
petition: None,
already_bound: false,
bar_placeable: true,
durability: None,
@@ -516,6 +517,7 @@ fn hovered_bag_tooltip_fills_itself_when_the_stats_land() {
slots.insert(
1,
ContainerSlot {
petition: None,
already_bound: false,
bar_placeable: true,
durability: None,
@@ -625,6 +627,7 @@ fn vendor_bag_hover_shows_sell_price_and_arms_the_pouch_cursor() {
slots.insert(
1,
ContainerSlot {
petition: None,
already_bound: false,
bar_placeable: true,
durability: None,
@@ -645,6 +648,7 @@ fn vendor_bag_hover_shows_sell_price_and_arms_the_pouch_cursor() {
slots.insert(
2,
ContainerSlot {
petition: None,
already_bound: false,
bar_placeable: true,
durability: None,
@@ -841,6 +845,7 @@ fn drag_across_two_slots_queues_the_same_move_a_click_pickup_would() {
slots.insert(
1,
ContainerSlot {
petition: None,
already_bound: false,
bar_placeable: true,
durability: None,
@@ -930,6 +935,7 @@ fn a_second_bag_window_feeds_and_paints_via_the_bag_bar() {
slots.insert(
1,
ContainerSlot {
petition: None,
already_bound: false,
bar_placeable: true,
durability: None,
@@ -1062,6 +1068,7 @@ fn open_backpack_with_a_five_stack(s: &mut UiScript) -> (f32, f32) {
slots.insert(
1,
ContainerSlot {
petition: None,
already_bound: false,
bar_placeable: true,
durability: None,
@@ -107,6 +107,7 @@ fn backpack_with_fitting_helm() -> benilla_ui::script::ContainerState {
slots.insert(
1,
benilla_ui::script::ContainerSlot {
petition: None,
already_bound: false,
bar_placeable: true,
durability: None,
@@ -319,6 +319,7 @@ fn chat_click_dismisses_a_stuck_spell_but_not_an_item() {
slots.insert(
1,
ContainerSlot {
petition: None,
already_bound: false,
bar_placeable: true,
durability: None,
@@ -32,6 +32,7 @@ fn one_item_backpack() -> ContainerState {
slots.insert(
1,
ContainerSlot {
petition: None,
already_bound: false,
bar_placeable: true,
durability: None,
@@ -25,6 +25,7 @@ fn one_item_backpack() -> ContainerState {
slots.insert(
1,
ContainerSlot {
petition: None,
already_bound: false,
bar_placeable: true,
durability: None,
@@ -237,6 +238,7 @@ fn escape_closes_an_open_stack_split_frame() {
slots.insert(
1,
ContainerSlot {
petition: None,
already_bound: false,
bar_placeable: true,
durability: None,
@@ -700,6 +700,7 @@ fn backpack() -> ContainerState {
slots.insert(
1,
ContainerSlot {
petition: None,
already_bound: false,
bar_placeable: true,
durability: None,
@@ -376,6 +376,59 @@ fn auto_unstuck_casts_and_closes() {
assert!(!s.eval::<bool>("return HelpFrame:IsVisible()").unwrap());
}
/// **The retail-only text is gone from the Home page** (director's call, 2026-08-29).
///
/// The page is entirely GlobalStrings off the player's own chain, so three of its strings still
/// pointed at `worldofwarcraft.com` — a dead PvP-policy link, and a closing paragraph directing the
/// player to Blizzard's forums and policy pages. The window overrides them before the markup
/// resolves.
///
/// This test exists because the failure mode is *invisible in a test suite*: nothing breaks if the
/// override is dropped, the page simply starts advertising dead links again. Asserted on the
/// rendered FontStrings rather than the globals, so it also catches the markup being re-pointed at
/// a different key.
#[test]
fn the_home_page_advertises_no_dead_retail_links() {
let s = setup();
s.run("ShowUIPanel(HelpFrame) HelpFrame_ShowFrame(\"Home\")")
.unwrap();
for frame in [
"HelpFrameHomePvpPolicyUrl",
"HelpFrameHomeText2",
"HelpFrameHomeIssue3",
"HelpFrameHomeText1",
] {
let text = s
.eval::<String>(&format!("return {frame}:GetText() or \"\""))
.unwrap()
.to_lowercase();
for dead in [
"worldofwarcraft.com",
"http://",
"www.",
".shtml",
"the forums",
] {
assert!(
!text.contains(dead),
"{frame} still carries retail-only text ({dead:?}): {text:?}"
);
}
}
// The PvP guidance itself is KEPT — only the sentence introducing the link went. A test that
// let the whole bullet be emptied would pass on an over-correction, which is the other way to
// get this wrong.
let pvp = s
.eval::<String>("return HelpFrameHomeIssue3:GetText()")
.unwrap();
assert!(
pvp.contains("PVP game mechanics"),
"the PvP guidance must survive the link's removal: {pvp:?}"
);
}
/// **The geometry oracle** (decision 0675): every element the transcription shares with the
/// reference file carries the reference's own `<AbsDimension>` numbers.
///
@@ -187,6 +187,22 @@ fn a_signers_charter_shows_the_sign_face_and_nine_rows() {
);
}
// The charter's own static furniture — the three labels, the nine rows and the instructions
// all draw, not merely hold text.
for part in [
"PetitionFrameCharterTitle",
"PetitionFrameCharterName",
"PetitionFrameMasterTitle",
"PetitionFrameMasterName",
"PetitionFrameMemberTitle",
"PetitionFrameMemberName1",
"PetitionFrameMemberName9",
"PetitionFrameInstructions",
"PetitionFrameCancelButton",
"PetitionFrameCloseButton",
] {
assert!(visible(&s, part), "{part} is on screen");
}
assert!(visible(&s, "PetitionFrameSignButton"));
assert!(!visible(&s, "PetitionFrameRequestButton"));
assert!(
@@ -348,6 +364,16 @@ fn the_registrar_opens_on_services_and_purchase_is_a_local_panel_swap() {
assert!(visible(&s, "GuildRegistrarFrame"));
assert!(visible(&s, "GuildRegistrarGreetingFrame"));
assert!(!visible(&s, "GuildRegistrarPurchaseFrame"));
// **The two service rows must be VISIBLE, not merely labelled.** This assertion is here
// because its absence shipped the bug: the rows inherited `hidden="true"` from a template
// modelled on the quest list's POOLED rows, which are shown one at a time by code. These are
// static and nothing ever calls `:Show()` on them, so both were permanently invisible — the
// window came up as a bare "Available Services" heading over blank parchment — while every
// `GetText()` assertion below passed, because a hidden button still knows its own label.
// Decision 0672's lesson in another key: a frame that loads is not a frame that draws.
for row in ["GuildRegistrarButton1", "GuildRegistrarButton2"] {
assert!(visible(&s, row), "{row} is on screen, not just loaded");
}
assert_eq!(
text(&s, "GuildRegistrarButton1"),
"Purchase a Guild Charter"
@@ -361,6 +387,18 @@ fn the_registrar_opens_on_services_and_purchase_is_a_local_panel_swap() {
s.run("GuildRegistrarButton1:Click()").unwrap();
assert!(visible(&s, "GuildRegistrarPurchaseFrame"));
assert!(!visible(&s, "GuildRegistrarGreetingFrame"));
// The purchase panel's own furniture, for the reason the services rows above are checked: a
// window whose parts load but do not draw passes every text assertion.
for part in [
"GuildRegistrarPurchaseText",
"GuildRegistrarCostLabel",
"GuildRegistrarMoneyFrame",
"GuildRegistrarFrameEditBox",
"GuildRegistrarFramePurchaseButton",
"GuildRegistrarFrameCancelButton",
] {
assert!(visible(&s, part), "{part} is on screen");
}
assert_eq!(calls(&s), "", "the swap sends nothing");
}
@@ -100,6 +100,7 @@ fn seed_items(s: &mut UiScript) {
slots.insert(
1,
ContainerSlot {
petition: None,
already_bound: false,
bar_placeable: true,
durability: None,