The macro window's four reds were four separate things, two of them bugs a
player meets.
**Every macro made through the icon chooser had a blank icon.** This realm
rewrote `Blizzard_MacroUI` to keep bare upper-cased icon names in its own
`MACRO_ICON_FILENAMES` — `UpdateMacroIconFilenames` strips `INTERFACE\ICONS\`
off `GetMacroIconInfo` — and `MacroPopupOkayButton_OnClick` hands that name
straight to `CreateMacro`. `icon_path` took any string as a finished path, so
`GetMacroInfo` answered `ABILITY_AMBUSH` and `MacroFrame_Update`'s
`macroIcon:SetTexture(texture)` resolved nothing. The engine composes the prefix
itself: `Interface\Icons\` is at `0x44ca64`, inside `UIMacros.cpp`'s own string
block between the `DeleteMacro`/`GetMacroInfo`/`GetNumMacros`/`CreateMacro`
registration names and `MACRONODE`, which is what says the macro code owns it.
A number is still a chooser index, and a string that already carries a separator
is still a path.
**Selecting a macro with no icon raised and took the whole details pane down.**
`Blizzard_MacroUI.lua:84-85` feeds `GetMacroInfo`'s texture to `SetTexture` and
to `strupper` with no guard, so a nil answered there is
`bad argument #1 to 'strupper' (string expected, got nil)` inside
`MacroFrame_Update` — no editor, no body box, no character counter. The
reference cannot be answering nil, or its own UI would raise the same way: a
macro with no icon of its own now reads as the client's own
`INV_Misc_QuestionMark` (`0x447fe4`, same UI block), which is also the name the
realm's chooser puts in row 1.
The other three were stale readings of a file the realm has rewritten, each
checked against the chain copy rather than guessed:
- the character counter is `MacroFrameCharLimitText:SetText(this:GetNumLetters()
.. "/" .. this:GetMaxLetters())` in the realm's own XML — "21/510", with no
`MACROFRAME_CHAR_LIMIT` and no "Characters Used" tail;
- the chooser shows `GetNumMacroIcons() + 1` rows, because
`UpdateMacroIconFilenames` inserts `INV_MISC_QUESTIONMARK` at index 1 ahead of
anything the engine offers, so every list index is one past the engine's;
- both tabs call `PanelTemplates_TabResize(0)` with no cap and read
`text="GENERAL"`/`text="CHARACTER"`, where stock passed -15, capped tab 2 at
150 and formatted the player's name into it. That test was *about* the name,
so it now checks the row is independent of it instead.
195 failures from 203, no regressions (`comm -13` over the sorted names).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn