v0.1.0-classic
What's new
HelloUI
v0.1.0 (2026-07-26)
- Build the layout when a profile is created, and switch it when one is chosen
A new profile changes the layout's NAME - "HelloUI - <profile>" - and nothing
created a layout under that name, so Edit Mode stayed on the old profile's
layout, IsActive answered no, and the login prompt asked for a layout the
player had already accepted on the profile they copied from. Every session.
Config's profile writes now call Layout:FollowProfile, which:
- does nothing at all if HelloUI's layout was not active before the switch.
"Asked, never applied silently" has to survive this: consent carries across
a profile switch, it is not created by one. Read BEFORE the switch, since
afterwards the question is about the layout we just moved to.
- ACTIVATES an existing layout rather than re-applying it. Apply rebuilds the
systems table from current settings, which is right when you ask for the
layout and wrong when you merely switch to a profile that has one: Edit Mode
saves your dragging into the layout, so a rebuild throws it away. New
Layout:Activate only changes which layout is active.
- builds one with Apply only when the profile has no layout yet - the case in
the report - and defers to PLAYER_REGEN_ENABLED if that lands in combat.
The Edit Mode nudge (show then hide the manager, which is what makes it re-read)
is now a shared helper instead of living inside Apply, since Activate needs it
too. IsActive takes an optional layout name for the same reason.
352 checks. One mutation initially survived - rebuilding instead of activating -
because the assertion held a table captured before the switch, and GetLayouts
hands back copies, so the stale snapshot kept the marker the live layout had
lost. It re-reads from the client now and the mutation fails. - Add an addon-list icon, cropped from the project art
64x64, matching HelloWarrior's, with the TOC's IconTexture pointing at it.
Cropped rather than scaled: HelloUI.png is a logo card - monogram badge, "Hello
UI" wordmark, tagline, mock UI around the border - and squashing 1254px of that
into 64 turns the wordmark into three grey smears. The HU badge is the part that
survives at icon size, so the crop is just the badge and its ring.
Framed by measurement rather than by eye, after two attempts that missed: a
labelled grid over the source put the ring and its four diamond points at
x 425-820, y 55-500, and the final 438px window at (629, 288) holds all four
points while clearing the mock health bars on the left and the mock minimap on
the right - both of which a looser crop dragged in as coloured specks.
Icon.png ships in the zip; HelloUI.png still does not. - Release plumbing, pointed at CurseForge project 1625608
The project ID on its own does nothing: it is read by the BigWigs packager,
and HelloUI had no packager config and no workflow to run it. Ported from
HelloWarrior, which is the family's working version of this.
.pkgmeta what ships in the zip
.github/workflows/release.yml fires on a v* tag, checks the tag against the
TOC version, packages, uploads to CurseForge
and cuts a GitHub Release
.github/changelog.sh builds the release notes with commit trailers
filtered out, which the packager cannot do
.gitignore the generated CHANGELOG.md, .DS_Store, .claude/
Two differences from HelloWarrior's copy, both about what this addon has that
it does not: Tests/ and .luacheckrc are excluded from the zip - the harness
stubs the entire client API to run under plain lua and would never be read in
game - and HelloUI ships no IconTexture, so nothing needs keeping for the
client.
Also tracks HelloUI.png, the CurseForge project image, and excludes it from the
zip for the same reason HelloWarrior excludes its own: the page needs it, the
player does not.
Still needed before a release can happen: the GitHub repo (HelloUI has no
remote yet) and a CF_API_KEY secret on it. Without the key the workflow still
cuts a GitHub Release and just skips the CurseForge upload. - Profiles: settings per profile, characters pick one
Every setting lived at the top level of HelloUIDB and applied to all 47
characters, with a sparse per-character override list beside it. That shape came
from the DragonflightUI profile being ported - one arrangement, one diverging
character - and it is the wrong shape for alts you play differently, because it
makes you enumerate the exceptions.
Settings now live in HelloUIDB.profiles[name] and HelloUICharDB stores which one
this character uses. Get and Set resolve through the active profile; there is no
account/override split left to reason about.
What deliberately does NOT go in a profile: xpBarTextOriginal and proxyOriginals- HelloUI's memory of what Blizzard's state was before it touched anything - and
the migration latches. Those are not preferences, and copying them per profile
would mean a profile switch could hand the client back the wrong original.
The Edit Mode layout follows the profile: Default keeps the bare "HelloUI",
anything else gets "HelloUI - <profile>", always Account-typed since a profile
is shareable and a character is not. That collapses layoutPerCharacter into the
profile choice - one decision instead of two that could disagree.
Migration, latched on profilesV1: the old top-level settings become Default, and
a character with overrides gets a profile named after itself seeded with them.
The name is not cosmetic - the old per-character layout was "HelloUI - <char>",
so the layout that character already has is the layout it keeps.
/hui char becomes /hui profile [use|new|delete]; the panel gains a Profile
section with a dropdown, following HelloBuffCap's, which is the family's only
precedent for one on this client.
343 checks. Eight mutations tried, all caught: profiles sharing one table, a new
profile starting blank instead of copying, settings left at the top level,
remembered originals swept into a profile, overrides ignored by the migration, a
deleted profile resurrected at login, the layout ignoring the profile, and
Default being deletable.
- HelloUI's memory of what Blizzard's state was before it touched anything - and
- File the addon under UI Overhaul
The same category DragonflightUI sits in, which is where somebody looking for
its replacement will look. One English line, matching every sibling - the family
declares a bare## Category:and none of them carry the ten localised variants
DragonflightUI ships, and the client falls back to this line for any locale that
has no override.
Worth noting the tension rather than pretending it away: the Notes line still
says "no art overhaul", and that is still true - the category describes the
shelf, not the method. - Darkmode is one switch
The desaturate toggle, the four per-area checkboxes and the tint colour are
gone. Nobody was going to tint the minimap but not the cast bar, and the grey
was three numbers with no way to change them short of editing saved variables.
On desaturates and tints every texture on the allowlist; off hands all of them
back. The tint is a constant in Darkmode.lua.
The entries lose theirareatag with the gating, and Status stops printing a
list of areas that can no longer differ. Three more keys join the retired list
and are deleted from saved variables - including darkmodeAreas, which the
harness now plants as a TABLE with two areas set false, since that is the shape
a real install carries. Darkmode must still cover everything with those on file. - Remove the button border feature
Taking ActionButtonTemplate's NormalTexture from Blizzard's alpha="0.5" to full
shipped on the reasoning that hiding the bar backdrop takes the per-slot
recesses with it. Seen on screen, the stock half-alpha border is preferred, and
looking at it is the only thing that settles a look.
Nothing writes that texture now in either direction - not to set it, not to
restore it - so the buttons carry exactly what Blizzard ships. The key joins the
retired list and is deleted from saved variables on sight.
The harness assertion inverts with the feature: the border alpha is set to 0.42
first, something Blizzard never uses, then ApplyAll must leave it there. Both
"the feature came back" and "a restore path that still writes 0.5" fail that,
where asserting a bare 0.5 would have passed the second one. - Stop asking for the layout that is already active
Layout:IsActive compared an index into the SAVED-only list GetLayouts returns
against activeLayout, which counts the presets first. On Era that is 1 against
3, so it answered "not active" for a layout that plainly was, and MaybeAsk
raised the login prompt every single session.
This is the same asymmetry that made the first version of Apply switch to a
Blizzard preset, and Apply has done the [presets..., saved...] rebuild ever
since. IsActive never did. Both go through one combinedLayouts helper now,
because having the rebuild in one caller and not the other IS the failure mode.
The harness had an assertion for this - "no prompt once the layout is already
active" - and it passed against the broken code, because askedThisSession was
already latched from the login prompt earlier in the run, so MaybeAsk returned
before it ever consulted IsActive. The latch moves onto the module so a test can
simulate the next login, IsActive is now asserted directly, and the other half
is covered too: a player sitting on somebody else's layout must still be asked.
All four mutations fail, including the exact shipped bug. - Stop the panel telling you to do what the layout already does
Two notes in the options panel predated the layout feature and had been wrong
ever since. The minimap section said position was Edit Mode's job and to go drag
it there; the chat note said the same about the chat frame. Both are placed by
HelloUI's own layout - Layout.lua writes Enum.EditModeSystem.ChatFrame at
BOTTOMLEFT clear of the flank block, and the Minimap system with an anchor and
its Size setting.
The minimap section was only that note once the time-of-day checkbox was
retired, so it goes entirely. The chat note goes too, and the layout controls
gain a "Bar layout" header and one accurate line: what the layout covers, and
that dragging afterwards is saved into it so re-applying is the reset. The
button's tooltip gains the same coverage list.
The same stale claim was in four more places - the README's opening, the panel
subtitle, the Config comment about chat, and Minimap:Status - all of which said
some variant of "position is Edit Mode's job and this addon stays out of it".
The honest version is that HelloUI writes positions into an Edit Mode layout
rather than anchoring frames behind the client's back, which is a different
statement and the one the design actually turns on. DESIGN.md's two out-of-scope
entries now say which half is out of scope: never SetPoint on those frames, not
never place them. - Remove the friends list feature
Class-colouring the friends list and the heart for notes containing <3 are
gone, module and all. They were the one thing here that decorated a frame
nobody had complained about - ported from the old profile because the code was
short and the heart was charming, which is not a reason.
Friends.lua is deleted, with it the FriendsFrame_UpdateFriendButton hook and
the C_FriendList reads, so the addon no longer touches the friends list at all.
The two keys join the retired list and are deleted from saved variables on
sight, exactly like the six settings that became behaviour - the list now
covers both cases, and the comment says which is which.
The panel loses its Friends section, which leaves the right column ending at
the minimap note. The harness loses the friends stubs, its scroll-frame model
and four assertions, and gains the two dead keys in the retirement check plus
their checkbox names in the no-checkbox-survives sweep. 311 checks. - Retire six settings into plain behaviour
Hiding the gryphons and bar backdrop, always-visible XP and reputation text,
hiding the time-of-day dial, class-colouring the player health bar, yielding
the cast bar to a sibling that draws its own, and the flat cast bar are what
HelloUI IS. They were switches because everything started as one, and a switch
implies a decision worth making - nobody installs this addon and then turns the
de-clutter off.
They now run whenever the addon is enabled.enabledis the only gate above
them, so/hui offstill hands every one back, and the restore paths stay
exercised because that is the same condition they always used.
The keys are deleted from saved variables on sight rather than left to rot: an
install that ever ticked one carries it forever otherwise, and a stale
hideBarArt = falsesitting in the file reads like a setting that stopped
working rather than one that was retired. Char overrides are cleaned too.
The panel loses six checkboxes and, with them, its "Status bars" and "Unit
frames" sections, which had nothing else in them. It gains one line listing
what is now unconditional - a panel that simply stopped mentioning them would
read as features that had been dropped.
Harness: an install with all six stored as false must come out with the keys
gone and the behaviour still on. Every target is forced into the WRONG state by
hand first, because asserting the right state without that passes on leftovers
from an earlier apply - which is exactly how the bar-art check went green
against a version still reading the retired key. 318 checks; the two mutants
that survive are equivalent, both masked by re-assertion hooks that enforce the
same outcome. - Scroll the options panel instead of overflowing the canvas
The Settings canvas is a fixed height - about 580 units, whatever the
resolution, since UIParent is always 768 tall - and this panel's left column
alone is taller than that. The canvas does not clip, so the last three controls
were drawn over the game: the layout checkboxes, all three buttons and the
status line, floating across the action bars below the Options window.
Rebalancing the columns would have bought exactly one release - the panel gained
two checkboxes this afternoon - so everything moves into a scroll child inside a
UIPanelScrollFrameTemplate. That is also what HelloHealer's settings panel
already does, with the same comment about growing content, so it is the shape
the family expects.
The scroll child's height is measured from the lowest element after each
refresh, not hard-coded: it IS the scrollbar's range, so a constant would either
cut off the last control or scroll into empty space, and the status line grows a
line when the character has overrides.
The way this regresses is a new widget parented topanelrather than
content- it escapes the scroll frame and floats over the game again. The
harness asserts the panel has exactly one child, that the scroll child exists,
and that a control from the far end of the layout really is inside it, so the
first check cannot pass by the panel merely being empty. All three mutations
fail. 311 checks. - Re-apply the cast bar style after Blizzard rebuilds the look
Blanking the border art was necessary and still not sufficient. CastingBarMixin
:SetLook("CLASSIC") rebuilds the bar's appearance from scratch:
self.Border:SetTexture("Interface\CastingBar\UI-CastingBar-Border") :1015
self.Text:ClearAllPoints(); self.Text:SetPoint("TOP", 0, 5) :1028
self.Text:SetFontObject("GameFontHighlight") :1029
and PlayerFrame_DetachCastBar calls it from EditModeCastBarSystemMixin
:ApplySystemAnchor, which runs on every Edit Mode layout update - at login,
after our styling pass, and on every close of Edit Mode.
So the border came back, the spell name went back to the full-size font, and
its box was re-anchored 5 units above the bar's top, which is where the classic
border art has room for it and a flat bar does not. That last part is the
"transparent top half": the name was drawn above the bar with nothing behind it.
Measured off the screenshots rather than guessed: the name's ink is 15px tall
starting 1px above the bar, while the countdown - which SetLook does not touch -
is 9px inside it. A partial undo, which is why it read as a styled bar whose
border had mysteriously returned rather than as Blizzard's own bar.
Fixed with a hook on the instance, the same shape as the UpdateBarFillTexture
one next to it. The harness now models SetLook faithfully and asserts all three
properties survive it; removing the hook fails three checks, and ignoring the
setting inside it fails a fourth. 307 checks. - Blank the cast bar's border art instead of hiding it
Hiding it worked until a cast COMPLETED, and then the outline flashed back for
about a second. Blizzard's own finish path is why:
self.Flash:SetAlpha(0.0); self.Flash:Show(); CastingBarFrame.lua:453, 571
if self.FlashAnim then self.FlashAnim:Play() end :743
<AnimationGroup parentKey="FlashAnim" setToFinalAlpha="true">
<Alpha childKey="Flash" fromAlpha="0" toAlpha="1" duration="0.08"/>
UI-CastingBar-Flash is the same 256x64 border shape as the border itself, so a
completed cast re-Shows it, animates it to full alpha, and setToFinalAlpha
leaves it there through the fade-out. Hide() loses to the Show; SetAlpha(0)
loses to the animation, which owns that property outright. This is the Show/Hide
fight Bars.lua already documents, one frame over.
A texture with no file draws nothing regardless of who shows it or what its
alpha is, so all three regions are blanked with SetTexture(nil) and their files
remembered for the restore. Blizzard's Show, its SetAlpha and its animation all
still run, and all still paint nothing. Shown-state is no longer recorded or
restored, because HelloUI no longer touches it.
The harness now replays that exact sequence - Show plus alpha to 1 - and asserts
the art stays blank, so the reported bug has a test. Going back to Hide() fails
five checks. 302 checks, 5 of 5 mutations caught. - Stop the spell name climbing out of the cast bar
Blizzard declares PlayerCastingBarFrame's Text as GameFontHighlight in a region
sized 185x16, inside a bar that is 13 tall. Centred in 13px, a 16px box stands
about 1.5px proud top and bottom - stock never showed it because the 256x64
border art drew over the overspill, and hiding that border is exactly what this
module does.
A smaller font rather than a clamped height: a FontString does not clip its ink
to its rect, so shrinking the box moves the problem instead of fixing it. Word
wrap off with it, since a long enough name takes a second line and leaves the
bar by a whole line height instead of a pixel and a half. Both are restored
when the style is switched off, the font from what the frame actually had
rather than from a constant.
HelloWarrior's bar was never affected - it is 13 tall with a small font - and it
now uses the same 195x13 and the same font object, so the two are one bar.
299 checks; the missing-font, missing-wrap and unrestored-font mutations all
fail. - Restyle Blizzard's cast bar to match HelloWarrior's
Two facts made this art removal rather than a replacement bar. Blizzard's
classic-style cast bar ALREADY draws the same fill texture HelloWarrior's does- CastingBarMixin:UpdateBarFillTexture sets Interface\TargetingFrame\UI-StatusBar
on that path - so the whole visible difference is the 256x64 border wrapped
around a 195x13 bar, a centred spell name, and no countdown. And there is no
CastTimeText on this build at all: UpdateCastTimeTextShown opens with
if not self.CastTimeText then return endand the Classic template declares
none, so the timer has to be ours.
So: hide the border and the flash that lights the same outline, a flat backdrop
behind the fill, the name to the left, a countdown on the right fed from the
mixin's own value/maxValue so the digits cannot disagree with the fill. No
shipped textures - the backdrop is a solid colour and the rest is Blizzard's.
The colour is the one thing needing re-assertion: UpdateBarFillTexture
re-applies a per-bar-type colour on every cast and state change, so it is
hooked on the INSTANCE, since Mixin() copies the method onto the frame and
hooking the mixin table would reach nothing.
Two harness fidelity bugs fixed on the way, both of which hid real behaviour:
SetPoint REPLACED the anchor list instead of accumulating, so a frame anchored
LEFT then RIGHT - how the spell name is stretched - reported only the RIGHT
point, and GetPoint(1) is meant to be the first anchor set. And SetAllPoints /
SetColorTexture were no-ops, so "the backdrop exists" would have passed for a
texture covering nothing and painted with nothing.
Also fixed a real bug luacheck caught in the restore path:f.Text and f.Text:GetPoint(1)truncates a multiple return to one value, so it recorded
the point and silently dropped all four offsets.
296 checks, 11 of 11 mutations caught.
- CastingBarMixin:UpdateBarFillTexture sets Interface\TargetingFrame\UI-StatusBar
- Yield the cast bar when a sibling is drawing its own
HelloUI's layout parks PlayerCastingBarFrame in the strip HelloWarrior's
cluster occupies, so on a Warrior they draw through each other. HelloWarrior
now draws its own bar at the top of its cluster; this switches Blizzard's off
while it is there.
The mechanism is Blizzard's own, not a workaround. ShouldShowCastBar is
self.showCastbar and self.unit ~= nil, and every path that would show the bar
goes through it, so SetAndUpdateShowCastbar(false) is a supported flag rather
than a Hide() we would have to defend against Blizzard re-showing. It is the
same call the client makes in OverlayPlayerCastingBarMixin, under the comment
"Disable real Player Cast Bar". Nothing re-asserts it, so neither do we.
Detection is by FRAME, not by addon. IsAddOnLoaded("HelloWarrior") is true on a
Priest with it installed, where it builds nothing at all - taking that Priest's
cast bar away would be a bug, and it is the mutation the harness now catches.
The real question is whether a sibling is drawing a cast bar right now, and the
honest test is its frame existing and its cluster being shown. Two named
globals, read-only; HelloWarrior does not know this file exists.
Known edge, stated rather than papered over:/hw bars offhides the cluster
and Blizzard's bar returns on the next apply pass rather than instantly. The
alternative is polling a sibling's visibility every frame.
Harness: CastBar.lua was not in the loader's file list, so the whole module
loaded in game and was never exercised offline - green suite, untested feature.
There is now a check that every .lua in the TOC is in that list, and dropping
the entry fails it. 276 checks, 7 of 7 mutations caught. - Pack the tracking button against the eye, and grey the minimap's gold
TWO CHANGES, both measured off a screenshot rather than reasoned about, since
reasoning about them is what produced the numbers being replaced.
The angle: 30 degrees came from the 33x33 FRAMES, and the frames are much
bigger than the art they carry. The visible gold ring is about 26.5 units
across, so frame-tangent still left a 15.7-unit hole - measured at 21.87px on
screen, twelve times the median gap between the buttons already packed on this
rim. The right target is the rim's own pitch: all twelve buttons ride one
circle (fit rms 0.30px) and the packed pairs sit a median 24.9 units apart.
At r=75 that is 19 degrees, chord 24.8.
Changing the default cannot do that on its own - applyDefaults only fills
missing keys and every install that has ever logged in has trackingAngle = 30
on file. So, a latched migration, value-guarded this time: a stored 30 moves to
19, a deliberate 45 does not. ResetAccount re-latches, which is the bug the
barsBaseV2 comment already records. The honest hole is in the comment: someone
who deliberately typed 30 is indistinguishable from someone who never touched
it, and gets moved.
The gold: darkmode's minimap area covered the map's own ring, the header and
the zoom buttons - and none of the gold RINGS around the buttons sitting on
top of them. Eight entries added, every one a name Blizzard's own XML gives it:
the four that share MiniMap-TrackingBorder (tracking, LFG, mail, battlefield),
the minimise button in normal and pushed state, and the compass pair. Still no
enumeration, so the four Hello*MinimapButtons cannot be caught by construction.
MiniMapWorldBorder is deliberately absent - MiniMap_ShouldShowWorldMapButton()
isreturn falseon Era, so it would only inflate the found count. The icons
inside the rings stay coloured: tint bezels, never payloads.
Harness: two of the rules this module is built on were unenforceable. A
children sweep of Minimap passed the entire suite because Frame had no
GetRegions, and the live-state check compared only the desaturation flag, which
SetDesaturated makes idempotent. Both are real now - a sibling button with an
anonymous ring, a live-state icon with its own tint - and both mutations fail.
264 checks, 10 of 10 mutations caught, including an angle clamp that would have
silently turned a deliberate 45 into 20. - Match the tracking ring to the LFG eye - Blizzard declares it 64, not 52
Third defect in the same 33-line file. Both buttons are 33x33 frames and both
rings are the same texture, Interface\Minimap\MiniMap-TrackingBorder, with no
TexCoords or colour on either - so the declared size is the entire difference:
LFGMinimapFrameBorder 52x52 at TOPLEFT (1,-1)
MiniMapMailBorder 52x52
MiniMapBattlefieldBorder 52x52
MiniMapWorldBorder 52x52
MiniMapTrackingBorder 64x64 <- the vanilla file again
64/52 = 1.2308, and a sub-pixel circle fit on the screenshot measured the two
rings at 42.91px and 34.98px, ratio 1.227 +/- 0.008. At 64 the ring overflows
its own 33x33 frame by 4 units; at 52 it fits, like every other button's.
The anchor moves with the size, which is not optional: the ring art is not
centred in its file, so resizing to 52 while leaving the border flush at (0,0)
slides the ring 3.47 units off the icon still sitting at Blizzard's (2,-2).
Copying the eye's (1,-1) verbatim sidesteps needing to know that fraction, and
the icon's own offset goes to (0,0) since it only ever cancelled the 64 ring.
SetSize on the regions, never SetScale on the frame: offsets live in the
anchored frame's own space, so SetScale(52/64) would render the 75-unit polar
vector at 61 against a rim at 70 and put the button back on the map - correct
art, broken placement, and the existing radius assertions could not see it
because they read the raw offsets. That mutant now fails 18 checks.
The target is read live off the eye's own ring and converted through both
effective scales, because a texture's GetWidth is in its own frame's space and
a button pack that scales the eye would otherwise leave the two rings equal on
paper and 25% apart on screen.
Switching the feature off now hands back both sizes AND both region anchors;
sizes alone would leave Blizzard's 64 ring around an icon centred for a 52 one.
Parent, shown state and the polar anchor still need the /reload that /hui off
already tells you about.
Harness: the two tracking regions and the eye's ring did not exist as stubs at
all, so every assertion here would have passed vacuously. 234 checks, and all
17 mutations tried are caught - including the two that survived the first pass,
a scale-blind copy and a foreign MiniMapTrackingFrame capturing the anchors. - Put the tracking button on the minimap's rim, not on the map
Anchoring it below the LFG eye translated a point on a circle, which cuts
the chord: the eye rides the rim 75 units from the map's centre, and 35
units straight down leaves 57 on a map whose rim is at 70. The icon was
therefore drawn on the map itself, next to the eye but inside the artwork.
Rotate instead of translate - take the eye's own offset from the centre
and turn it 30 degrees, which preserves the radius by construction. The
angle is a setting, nudgeable with/hui tracking <degrees>, because the
rim is shared with every other addon's minimap button and nothing in here
can see which slots those have taken.
Also:/hui minimapprobenow prints the three radii, which is the one
line that would have made the mistake obvious, and the harness gained
real frame rects - GetLeft answered 0 for everything, so every frame
shared a centre and any placement would have passed. - Show the tracking button - Blizzard never does at login
The probe settled it in one line:
tracking parent=MinimapBackdrop shown=false alpha=1.00 level=5 at 1219,683
lfg parent=MinimapBackdrop shown=true alpha=1.00 level=6 at 1219,718
GetTrackingTexture() = 135725
Correct parent, correct position directly under the LFG eye, right icon, full
alpha, and hidden. The positioning was right two attempts ago; the frame was
simply never shown, and every hypothesis I had been testing - wrong parent,
behind the map, zero alpha - was wrong.
MinimapTrackingSimpleMixin only shows it from MINIMAP_UPDATE_TRACKING, which
fires when tracking CHANGES. Log in with tracking already active and the frame
stays at its XML hidden="true" forever. That is a second Era bug in the same
frame, on top of the missing parent attribute, and both hide in the shipped
client because only classes with a tracking ability ever see it.
The fix is the three lines Blizzard's own handler runs, also run once at apply,
and mirrored on MINIMAP_UPDATE_TRACKING so it stays in step afterwards. Hidden
again when nothing is tracked, matching Blizzard.
Clock offset defaults to the 1, -1 dialled in live, replacing Blizzard's 3, 1.5- which is what the nudge command was for, and it took one round trip instead
of the three my guessing had used.
191 checks. Mutation-tested by positioning the frame without showing it, which
reproduces exactly what was on screen.
- which is what the nudge command was for, and it took one round trip instead
- Make the clock offset nudgeable; probe the tracking button's real state
Two aesthetic details I have now guessed at three times between them, so this
stops guessing and gets data instead.
THE CLOCK DIGITS. Blizzard's x=3 is an attempt at optically centring them in
box art that is not symmetric - the button's own HitRectInsets are 8 left and 5
right, implying the visible box centre is about 1.5 off, not 3. Whether 3 is
right is not something the source can settle and certainly not something I can
read off a JPEG, so the offset is a setting nudgeable live with
/hui clock <x> <y>, defaulting to Blizzard's own with the half-pixel y
rounded. Two seconds per attempt instead of a round trip each.
THE TRACKING BUTTON is invisible for a reason none of my hypotheses can
distinguish - wrong parent, behind the map, never shown, zero alpha - so
/hui minimapprobenow reports parent, shown, alpha, frame level and rect for
the button, its icon, LFG, the backdrop, the minimap and the clock, plus what
GetTrackingTexture() actually returns. That last one separates "HelloUI moved
it somewhere invisible" from "Blizzard never showed it".
No behaviour change to the tracking fix itself pending that output. - Anchor tracking under the LFG icon; fix the clock by compensating its scale
THE TRACKING ICON VANISHED because of a guard I added in the same commit that
moved it - "if it already has a parent, somebody else owns it, leave it alone".
MiniMapTracking's parent after a reload is whatever the parentless XML leaves
it with, the guard read that as somebody else's claim, and skipped the fix
entirely. Speculative defensiveness for a case never observed, breaking the
case that was. The guard is gone, and the harness now hands the frame a parent
up front so re-adding it fails the tests.
It is now anchored TOP to LFGMinimapFrame's BOTTOM rather than to fixed
coordinates, so it sits directly under the eye on the ring's edge and follows
it wherever it moves. A hidden frame still reports its position, so this works
before either icon is shown. Blizzard's own backdrop position stays as the
fallback for when LFG is absent.
THE CLOCK, properly this time. The previous attempt rounded Blizzard's
half-pixel anchor, which could not work: the minimap is scaled to 110%, so an
offset of 2 lands at 2.2 screen pixels and no integer helps. The cause is the
scale, so the button's own scale is compensated to bring its EFFECTIVE scale
back to UIParent's - the digits then render at native size on the grid, and the
clock stays its original size while the map around it is bigger. Only once
that holds is rounding the anchor meaningful, so it is done afterwards rather
than instead. Re-applied on UI_SCALE_CHANGED and DISPLAY_SIZE_CHANGED, since
the compensation depends on a scale that can change.
185 checks. Both mutation-tested: re-adding the guard, and rounding the anchor
without compensating the scale. - Default the clock text rounding off - it did not help
Reported as making the clock worse, so it is off.
The reasoning was that Blizzard anchors the ticker at a half pixel and text off
the grid renders smeared. That much is true, but the fix does not follow: the
minimap is scaled to 110%, so an offset of 2 renders at 2.2 screen pixels and
no integer offset lands on the grid while that scale is in force. I moved the
text half a pixel for no benefit. Blizzard's 1.5 may also be optical rather
than accidental, in which case rounding it is actively wrong.
Kept behind a setting rather than deleted, because at 100% the arithmetic does
hold and it may be worth something there. The test covers both states.
The likely real cause of the soft clock is that 110% scale, which is a plain
trade rather than a bug: minimapSize 5 is crisp at the original size, 6 is
bigger and slightly soft, and 200% is the next whole-number scale and far too
large. Documented at the setting so the choice is visible. - Fix the clock's half-pixel text, and put tracking where Blizzard puts it
THE CLOCK. Blizzard anchors the ticker at a half pixel -
<Anchor point="CENTER" x="3" y="1.5"/> in Blizzard_TimeManager - and a font
string off the pixel grid renders smeared, which is what reads as misaligned.
Only the y is rounded; the x=3 is deliberate, centring the digits inside dial
art that is not symmetric. Blizzard_TimeManager is LoadOnDemand, so it is
re-applied if the addon arrives after us.
TRACKING vs LFG. Yesterday's fix used DragonflightUI's offsets, which put the
button in the ring's upper left - straight on top of LFGMinimapFrame at
MinimapBackdrop TOPLEFT (25, -28). Blizzard's own vanilla anchor, TOPLEFT
(11, -26), collides just as badly: 14px apart with 33px icons.
The answer was already in the tree. The same frame name is declared in
MinimapTracking_Dropdown.xml - the variant every non-vanilla flavour loads -
as parent="MinimapBackdrop" at TOPLEFT (5, -64), a full row below LFG. So that
is where Blizzard puts this button when the parent attribute has not gone
missing, and it is the right answer rather than merely a different one.
Also added: a guard so a tracking button somebody else has already parented is
left alone, and the minimap size is now a config value rather than a constant,
because a non-integer scale puts the whole minimap subtree on fractional
pixels - if the clock text still looks soft at 110%, 100% is the value that
does not, and the comment says so.
185 checks, all three mutation-tested including the naive (11,-26) position.
SetParent was stubbed as a no-op in the harness, which let the reparent "pass"
while doing nothing - the same gap SetWidth had. It is real now. - Chat above bar 5, bigger minimap, tracking button back on the map, no paging arrows
Four fixes from an Edit Mode screenshot, three through the layout and one a
genuine Blizzard bug.
CHAT UNDER BAR 5. Switching bar 5 on put a 4x3 block exactly where Blizzard
parks the chat frame, so they overlapped. Chat is now positioned in the layout,
lifted above the block. This is not a reversal of the earlier refusal to pin
the chat frame: that was about fighting Edit Mode with SetPoint on a frame Edit
Mode owns and silently re-anchors. Writing it into the layout is the sanctioned
route, the same mechanism every other frame here uses, and it stays draggable
afterwards.
MINIMAP one slider step larger - raw 5 is 100%, raw 6 is 110%.
THE TRACKING BUTTON is broken in Blizzard's own XML on Era.
Blizzard_Minimap/Classic/MinimapTracking_Simple.xml declares MiniMapTracking
with NO parent attribute and an unqualified TOPLEFT anchor, and the
accompanying Lua never reparents it - so it lands in the top-left corner of the
screen beside the player frame instead of on the minimap. The non-vanilla
variant of the same file has parent="MinimapBackdrop"; the vanilla one lost it.
Earlier research flagged this as uncertain because only classes with an active
tracking ability ever see it; the screenshot confirms it. Reparented onto the
minimap at DragonflightUI's offsets.
BAR 1'S PAGING ARROWS hidden through Blizzard's own HideBarScrolling, written
only on the main bar since that is the only one carrying the setting.
175 checks, each of the three layout changes mutation-tested. One existing
assertion used the minimap as its "untouched system" control and had to move to
a system HelloUI genuinely never positions, now that the minimap is one it does. - Position bars 6-8 even though they ship switched off
Answering "why are 6, 7 and 8 off?" turned up a latent bug behind the answer.
They are off because DragonflightUI's own defaults have activate = false for
all three, and the shipped default is meant to be its base action bar UI. Not
because Classic Era cannot use them - MultiBar5/6/7 are real on this build,
with action pages 13/14/15, their own MULTIACTIONBAR5-7 bindings and Blizzard's
own visibility checkboxes.
But the layout was not positioning them, and Blizzard's preset parks those
three in the MIDDLE OF THE SCREEN - TOP relative to UIParent CENTER at 0, -50
and -100. So switching one on would have dropped a row of buttons across the
player's view, with nothing to suggest HelloUI had anything to do with it. They
now continue the stack upward above the stance/pet row, on the same argument
already applied to MultiBarLeft: a bar that is off should still land correctly
the moment it is turned on.
170 checks. Mutation-tested by removing the three entries again. - Scale the status bar's border art, not just its frame
"In Edit Mode it looks right, but the graphics are wrong" was the clue that
identified this precisely: the frame was the correct width and the artwork was
not.
The container's border is a fixed chain of textures anchored left-to-right
from its left edge - standalone is 16 + 240 + 256 + 256 + 256 = 1024, the
main-menu set is 4 x 256 - and none of them follow the frame. So resizing the
container moved Edit Mode's selection box and left the bar drawn on screen at
its original 1024, which is why every measurement I took said the geometry was
correct while the screen disagreed. The probe could not see it either: it
reports frame rects, and the frame was right.
Every segment is scaled by the same factor rather than stretching the middle
ones, because each carries its own TexCoords into the source image and
stretching them unevenly distorts them against each other. The segments are
named explicitly rather than walked, because the 9x9 end caps live in the same
parentArray and are anchored to the first segment - they keep their natural
size.
Originals are recorded, so /hui off restores the art along with the width.
164 checks. Mutation-tested by resizing the frame without the art, which
reproduces the reported symptom exactly. - Make empty action slots a setting
The probe confirms the geometry is now exact: stack 478..886 and status bar
478..886 in screen space, zero overhang either side; three rows 38 apart at 35
tall; both flanks 150x112, that is 4x3, sitting 20 and 21px clear of the stack.
Widths, pitch, alignment and shape are all where they should be.
What is left is that turning AlwaysShowButtons on - done so the flanking bars
would read as 4x3 blocks rather than a couple of stray buttons - means a bar
you have not filled now shows a full grid of empty squares. That is arguably
worse clutter than the problem it fixed, and which way it should go depends
entirely on whether those bars are used, so it is a setting rather than a
decision made on the player's behalf.
Default stays on, matching DragonflightUI's alwaysShow and the reference
screenshot. It lives in the Edit Mode layout, so it needs /hui layout to take
effect, and the tooltip says so.
161 checks, mutation-tested in both directions. - Derive the XP bar width from the real action bar, through effective scales
The bar overshot the stack on the right because 454 was being set in the
container's own coordinate space, not screen pixels.
StatusTrackingManagerMixin:UpdateBarVisuals calls SetScale(self.ClassicScale)
on the manager, so a container at scale 1.4 renders a "454" width as 636 on
screen - wider than the stack by exactly the overhang reported.
The width now comes from MainActionBar:GetWidth() at apply time, converted
through both frames' effective scales, so it matches whatever the stack
actually measures whatever either scale happens to be. The configured 454
remains only as a fallback for when the main bar cannot be read.
/hui layout probe now prints the two spans side by side in screen space, with
the left and right overhang called out, so any residual mismatch is a number
rather than an impression.
159 checks. Mutation-tested by ignoring the scale conversion, which is now
caught rather than shipped. The mock's action bars had the default 100px width,
which would have let a wrong derivation look right - they are 454x36 now. - Set the XP/reputation bar width directly instead of scaling it
Scaling was the wrong lever and produced both complaints at once. Edit Mode's
"Size" for these bars is SetScale(value / 100), so narrowing the bar squashed
its height - hence "too thin" - and the slider floors at 50%, which on a
1024px container is 512 against a 454px stack, hence "still too wide". No
setting in Edit Mode can express what was wanted, so Size goes back to 100%
and the width is set directly.
Three frames per container, because they do not follow each other:
StatusTrackingBarTemplate's inner StatusBar carries an explicit
<Size x="1024"> and is anchored only at RIGHT, so it does not track its
parent's width, and StatusTrackingBarContainerMixin:InitializeBars stamps that
size in again at creation from the container's width. Missing any one of the
three leaves a bar that is visibly the wrong width.
Re-asserted after UpdateBarVisuals, which Blizzard calls on anchor changes and
from UIParent_ManageFramePositions. The hook is on the manager INSTANCE, not
StatusTrackingManagerMixin - Mixin() copies functions onto the frame at
creation, so hooking the mixin table would reach nothing.
Original widths are recorded so /hui off restores them, same contract as every
other thing this addon changes.
156 checks. Mutation-tested by skipping the inner StatusBar and by dropping the
re-assert hook. The harness had SetWidth stubbed as a no-op, which would have
let this pass while doing nothing - it is real now. - Position the cast bar and narrow the XP/reputation bars
CAST BAR. Blizzard's preset parks it dead centre of the screen; it now sits
just above the action bars at DragonflightUI's own default of CENTER/BOTTOM
+245. LockToPlayerFrame is written explicitly rather than inherited, because
if it were ever on the cast bar would attach itself to the player frame and
ignore the anchor entirely - the same reasoning as stating orientation on the
side bars.
XP/REPUTATION WIDTH, and an honest limit. The status tracking bar's "Size" is
not a width, it is a scale: Edit Mode does SetScale(value / 100) on a container
that is 1024 wide. The slider runs 50 to 130 in steps of five, so the narrowest
it offers is 50%, which is 512px against the 454px stack. Closer than the
full-width 1024 it shipped at by a long way, but not an exact match, and it
cannot be made one through this setting - 454 would need 44% and the slider
does not go there. Scaling also shrinks the height, which happens to be right:
the bar ends up slim rather than merely short, matching the reference.
146 checks, both mutation-tested - reverting the cast bar to the preset anchor
and the status bars to full width each fail their assertions. - Stack the centre bars flush; make the side blocks 4x3
Both reported problems came from the same class of mistake - assuming a value
instead of reading it.
THE GAP BETWEEN THE ROWS. ActionButtonTemplate is 36x36, so 100% icon size is a
36px button, not the 45 I had assumed when picking a 48px row pitch. That left
a 12px gap. The pitch is now derived from the template - button plus the 2px
padding, so 38 - which is the same pitch as the gap between buttons within a
bar, and the three bars read as one continuous grid instead of three bars with
air between them. FLANK_X follows from the same arithmetic: half the
twelve-wide stack (227) plus half a four-wide block (75) plus a 20px gap.
THE SIDE BLOCKS WERE 3x4. Blizzard's preset sets both MultiBarRight and
MultiBarLeft to Orientation = Vertical, and on a vertical bar NumRows counts
COLUMNS - so rows=3 produced three columns of four rather than four columns of
three. Every positioned bar now states its orientation explicitly instead of
inheriting whatever the preset happened to have, which is the more defensible
rule anyway: the layout should not depend on preset values it never set.
139 checks. The row-pitch assertion now expresses the actual intent - never
overlapping a 36px button, and never exceeding button+padding, so a future
change cannot silently reintroduce either the overlap or the gap. Orientation
is mutation-tested by letting the side bars inherit the preset again. - Show empty button slots, and add a geometry probe
The flanking bars were rendering as one or two stray buttons rather than the
4x3 blocks the arrangement assumes. Blizzard's preset has AlwaysShowButtons
off, so empty slots are hidden and a mostly-empty bar looks broken rather than
empty. DragonflightUI's own default is alwaysShow = true, so that is now set on
every bar the layout positions.
/hui layout probe prints what the frames actually report - left, bottom, width,
height and shown, for every bar plus the status tracking container, the micro
menu and the bags, against the screen size. Everything in this module has been
written blind: the harness can prove the layout table is correct but has
nothing to say about where a frame lands, and reading pixel positions off a
JPEG is guesswork that has now been wrong several times in a row. This turns
the remaining questions into arithmetic.
The doubled screenshot text is confirmed not to be HelloUI - it survives
/hui off, UIErrorsFrame is not an Edit Mode system, and no installed addon
registers the screenshot events. - Migrate the saved bar set; bigger buttons, tighter rows, park the micro menu
WHY BARS 5-8 WERE ALL DARK. Changing a default cannot change saved state.
applyDefaults only ever fills in MISSING keys, so an install that had already
savedbarsOff = { bar5 = true }kept it, and the new default helpfully added
6, 7 and 8 on top - four dark bars instead of three lit ones. Defaults cannot
migrate; migrations have to. There is now a one-time, latched migration that
replaces the profile-derived set with DragonflightUI's base one, and a test
that runs it against exactly that old database.
While fixing it: /hui reset wiped the migration latch along with everything
else, so the migration would re-run at the next login and undo any bar the
player had changed since. Reset already installs the base set, so it now marks
the migration done.
BUTTONS WERE TOO SMALL AND THE ROWS TOO AIRY. Both came from taking
DragonflightUI's buttonScale of 0.8 literally: that scales ITS buttons, and
matching the number rather than the result gave 36px icons with a 4px gap that
read as loose. Icons are 100% now and the row pitch is 48 - the button plus a
hair - which is simultaneously bigger buttons and less air. FLANK_X follows to
400, still derived rather than eyeballed: half the stack (282) plus half a
block (94) plus a gap.
THE MICRO MENU AND BAGS were sitting on top of the reputation bar. Both are
Edit Mode systems, and Blizzard's default puts them along the bottom
centre-right - which is precisely where the rep bar went once it was pinned
under the stack. Parked in the bottom-right corner, bags below the menu.
Not touched: the doubled "screenshot captured" text. UIErrorsFrame is not an
Edit Mode system and nothing here goes near it, so this is very unlikely to be
HelloUI - see the note to the player for how to confirm in one command.
133 checks. Mutation-tested against removing the migration, against reset
forgetting to re-latch, and the old-database case is now a first-class test
rather than something the fresh-install harness could never reach. - Default to DragonflightUI's base bar set; make barsOff authoritative
Two changes, and the second is a deliberate reversal.
THE DEFAULT IS NOW THE BASE UI, not the old profile's deviations:
DragonflightUI's own defaults are bars 1-5 shown and 6-8 off (activate=false
for those three), so that is what ships. The previous default had bar 5 off
purely because the saved profile happened to say so, which was the wrong
baseline to have generalised from.
barsOff IS NOW AUTHORITATIVE. Anything in it is hidden, anything not in it is
shown. The earlier rule would only ever turn a bar OFF, added in response to a
review finding that force-enabling silently overwrote a setting HelloUI does
not own. That rule cannot express "the base UI has bar 5 up": it left bar 5
dark forever because the player's stored Blizzard setting had it off, and no
amount of changing the default would have fixed it.
What made the old behaviour a bug was that it was silent and unasked-for. What
makes this acceptable is different on every count: it is the documented job of
the feature, every bar is individually switchable, and the pre-existing value
is remembered in saved variables so /hui off hands it straight back. That
restore is the safeguard the reversal rests on, so it is the thing the new
tests actually check - and it is mutation-tested by dropping the remember step,
which fails two assertions.
127 checks. - Report which layout systems were positioned, instead of skipping in silence
The reputation bar is still not under the stack, and the reason the cause was
not obvious is a bad guard of mine: if the status tracking bar enums are absent
the geometry entries are dropped without a word, so the layout reports success
while two of nine systems were never touched.
/hui layout status now names them: how many of the wanted systems were found
and positioned, which ones were not found, and separately whether the status
tracking enums exist on this client at all.
No behaviour change to the layout itself. This is to stop guessing at pixel
geometry from screenshots. - Fix the layout activating a Blizzard preset; match the reference screenshot
THE BUG. Accepting the prompt switched to Blizzard's Modern preset instead of
the HelloUI layout. C_EditMode.GetLayouts() returns only the SAVED layouts, but
activeLayout - and every index SaveLayouts cares about - counts the PRESET
layouts first. So an index computed against the saved-only list is short by the
number of presets and lands on one of them. The list has to be rebuilt as
[presets..., saved...] before any index means anything, and that combined list
is what SaveLayouts wants handed back.
Three more things were missing from the same sequence: new layouts belong after
the last entry of their own type rather than on the end, SetActiveLayout is the
activation path rather than OnLayoutAdded, and saving does not apply - opening
and immediately closing EditModeManagerFrame is what makes Edit Mode re-read.
Also gated on EditModeManagerFrame.accountSettings, since writing before Edit
Mode has its data produces a layout that saves and then evaporates.
None of that is guessable from the API documentation. It is lifted from
LibEditModeOverride (plusmouse, MIT), the known-working implementation of this,
which DragonflightUI vendors - reimplemented in about forty lines rather than
vendored, under the no-libraries rule, and credited in the source.
MATCHING THE REFERENCE. Measured off DragonflightUI's own default screenshot:
row pitch tightened from 44 to 40 so the rows sit snug the way the screenshot
shows while still clearing a 36px icon, stance and pet centred above the stack
rather than offset apart, and - the visible gap - the XP and reputation bars
are now positioned too. Blizzard's preset anchors those to
StatusTrackingBarManager, which strands them among the button rows once the bar
art they were sitting on is hidden; they are pinned under the stack instead.
Only action bars get NumRows/IconSize/IconPadding, since the status bars carry
a different setting enum.
MultiBarLeft stays switched off - the old profile had it off, and the layout
positions it correctly for whenever it is turned on.
124 checks. The harness stub now models the asymmetry properly - it derives the
active layout name from presets-then-saved, as the client does - so the
mutation reproduces the reported symptom exactly: "got Modern, want HelloUI".
The insert position is clamped so a miscount degrades to a wrong slot rather
than a hard throw from table.insert. - Ask before applying the layout; fix the overlap; visible button borders
Four things, one of which was a real bug the harness caught.
THE LAYOUT OVERLAPPED. Chaining bar-to-bar was wrong: an Edit Mode action bar
frame is shorter than the buttons it contains, so a 2px gap measured from the
frame's top edge lands inside the row above, and the screenshot showed rows
~26px apart with 36px icons. Everything is UIParent-relative at explicit
offsets now, with a deliberately generous 44px step - a gap slightly too big
reads as a choice, one pixel too small reads as a bug. The flanking 3-row
blocks sit at +/-330 from centre. Those numbers are estimates and are meant to
be nudged in Edit Mode; that is the point of writing a layout rather than
enforcing one.
LAYOUT WAS IN ns.MODULES, which meant ApplyAll called Layout:Apply on every
PLAYER_ENTERING_WORLD and every options change - rewriting the Edit Mode layout
constantly and undoing any bar the player had dragged. That is precisely the
behaviour the rest of this design exists to prevent, and it would have been
invisible in play until someone moved a bar and watched it snap back. Layout is
now wired by hand for Init and Status only, with the reasoning in a comment so
nobody adds it back.
ASK, DON'T DECIDE. The silent one-time auto-apply is gone, replaced by a login
prompt with Use it / Not now / Never. It asks once per session and only when
the layout is not already active, so accepting retires the question for good.
The applied-once latch went with it - the prompt subsumes what it was for.
BUTTON BORDERS. Hiding the bar backdrop took the per-slot recesses with it, and
ActionButtonTemplate's own NormalTexture ships at alpha 0.5 because it was only
ever meant to sit on top of that backdrop - so the buttons read as invisible.
Full alpha, using Blizzard's own UI-Quickslot2. Deliberately not
DragonflightUI's answer, which replaced NormalTexture with its own atlas slice
and would mean shipping textures.
Also: opting a character out of the shared layout is now a per-character
override rather than an account setting, which is what was asked for and what
Config's override machinery was built for. Everyone shares one layout until a
particular character ticks out.
114 checks. Mutation-tested against re-adding Layout to ApplyAll, against
leaving the border at 0.5, and the harness now asserts the stacked bars clear a
36px icon so the overlap cannot come back. - Layout reset, and per-character layouts
Two follow-ups, both smaller than they sound because Edit Mode already has the
machinery.
RESET. It turned out to already exist without being named: Edit Mode saves
your dragging into the layout itself, so rebuilding and overwriting is exactly
"put it back how HelloUI had it"./hui layoutwas already doing that. Added
Layout:Reset as an explicit alias, relabelled the options button to "Apply /
reset the bar layout", and said so in the tooltip - the behaviour was fine, the
discoverability was not.
PER-CHARACTER. Edit Mode layouts carry a layoutType, and Character layouts are
only visible to the character that owns them, so this is a choice of enum value
plus a name rather than any new mechanism. Off by default, matching the old
profile where 47 characters shared one arrangement; on, each character gets
"HelloUI - <name>" typed Character, so tuning the priest stops moving the
warrior's bars. The auto-apply latch follows the mode - account-wide latches
once for the account, per-character once per character - so switching the
setting gives each character its own copy as it logs in rather than doing
nothing until asked.
Blizzard caps layouts at 5 per type (EditModeMaxLayoutsPerType), counted
separately for Account and Character. Checked before inserting, with an error
naming which type is full, because SaveLayouts past the cap fails opaquely.
Switching modes deliberately leaves the previous layout in place rather than
deleting it - this addon does not delete a player's layouts, and one stale
dropdown entry is a much better failure than a removed one.
107 checks. The reset test drags a bar and resizes it in the saved layout, then
asserts re-applying restores both; mutation-tested against typing the
per-character layout as Account. - Build the DragonflightUI bar layout as an Edit Mode layout
The de-clutter was landing correctly but the bars were in Blizzard's
arrangement, not DragonflightUI's. That gap was invisible to the method this
addon was scoped by: AceDB records deviations from defaults, and the layout was
DFUI's default, so it could never appear in the saved profile. Same blind spot
as the gryphons, one size larger.
This does not break "delegate position to Edit Mode" - it is that rule taken
seriously. Rather than fighting Edit Mode with SetPoint on frames it silently
re-anchors on every layout save, spec change and Edit Mode close, HelloUI
writes a real Edit Mode layout and lets Edit Mode apply it. Afterwards the
addon is uninvolved: it is the player's layout, editable and persistent like
any other.
Non-destructive by construction. It adds a layout named HelloUI and never
writes to an existing one, so the worst case is one extra entry in the
dropdown. Applied once and latched on layoutAppliedV1 - the one-time-migration
idiom the era-1159 fork used for its minimap tuck - because re-applying every
login would undo any bar the player had since dragged, which is precisely the
behaviour the rest of this design exists to avoid.
Geometry is DragonflightUI's own defaults from its Actionbar.lua: buttonScale
0.8, padding 2, bars stacked upward from the status bars, the two 3-row blocks
flanking at +/-64. Edit Mode stores those two scalars pre-conversion
(display = raw * stepSize + minValue), so 80% is raw 3 and 2px padding raw 0.
Bar-to-bar anchoring rather than computed pixel offsets, because row height
depends on icon size and Blizzard already knows it.
Three shape traps, all found by reading the client source and all now covered
by the harness:- settings is an ARRAY of {setting, value} pairs, not the map form the preset
files use. A map write silently corrupts the array instead of erroring. - anchorInfo.relativeTo is a frame NAME string.
- isInDefaultPosition must be set false on anything moved, or Edit Mode treats
the system as untouched and re-slams it to the preset anchor.
The systems array is built from a copy of Blizzard's own preset rather than by
hand, so every system HelloUI does not care about carries Blizzard's correct
entry and the layout cannot be missing one.
99 checks. Mutation-tested against the isInDefaultPosition flag and the
map-vs-array trap; the latter now fails cleanly rather than crashing.
- settings is an ARRAY of {setting, value} pairs, not the map form the preset
- Hide the gryphons and main bar backdrop
The design's evidence table markedgryphons = 'NONE'andhideArt = trueas
"nothing to do - DFUI's own art". That reasoning was correct and beside the
point: those settings were DragonflightUI declining to draw art of its own, so
there was nothing to port, but Blizzard's gryphons and bar backdrop are right
there on a stock client and the profile is unambiguous that they were not
wanted. Reading the mechanism and losing sight of the intent dropped a real
feature; the screenshots put it back on the agenda.
Implemented as appearance rather than position, which is what makes it
HelloUI's job at all - philosophy #2 is about anchors, and philosophy #1 is
explicitly "keep Blizzard's frames, change their appearance". It hides the same
two frames Blizzard's own Edit Mode HideBarArt drives - MainMenuBar and
MainActionBar.EndCaps, per MainActionBarMixin:UpdateEndCaps - directly, rather
than through EditModeManagerFrame:OnSystemSettingChange, which would write
manager state in our taint context and persist into the player's saved layout.
Hiding two unprotected art frames does neither and reverses cleanly; restoring
asks UpdateEndCaps which of the two belongs on screen rather than guessing,
since that depends on whether the bar has been moved.
Checked before shipping: MicroMenuContainer and BagsBar are children of
UIParent on this build, so the micro menu and bags survive. The latency strip
does not - it lives inside MainMenuBar - which is exactly what Blizzard's own
setting does too.
The re-hide hook goes on the MainActionBar INSTANCE, not MainActionBarMixin.
Mixin() copies functions onto the frame at creation, so hooking the mixin table
reaches nothing - the same mistake that made the old UpdateHotkeys hook inert,
and now mutation-tested so it cannot come back. - Fix nine defects found by adversarial review; drop the chat pin
A 33-agent review against the 1.15.9 client source produced 28 findings; 20
survived independent refutation. The seven that changed behaviour:- Bars force-enabled every proxy bar it was not hiding. Any bar the player had
switched off in Blizzard's own Action Bars options came back at the next
login - HelloUI silently overwriting a setting it does not own, in the one
module whose whole argument is that Blizzard's setting is the right
mechanism. It now only ever writes to turn a bar off, remembers what the
player had in saved variables, and restores exactly that. - Options stored
v or nilfor nested checkboxes, so unticking deleted the key
and Config's applyDefaults resurrected the default on the next ADDON_LOADED.
Unticking "bar 5" worked for the session and was silently undone forever. - Friends:Refresh walked FriendsListFrame.ScrollFrame, which does not exist:
the scroll frame is the global FriendsFrameFriendsScrollFrame, declared with
no parentKey. The nil guard turned the whole repaint path into a no-op. - Friends repainted un-coloured rows with FRIENDS_WOW_NAME_COLOR to "clean up
after recycling", which turned every offline friend blue. Blizzard sets the
right colour before our hook runs; the fix is to leave it alone. - StatusBars kept the player's xpBarText in a file-local. After one /reload the
capture was of HelloUI's own written value, so disabling the feature restored
"1" forever. Now persisted. - Suppressing bar 1 left ActionBarPageNumber's page arrows invisible but fully
clickable. Mouse state now covers the bar's child frames, recording each
one's prior value so nothing Blizzard had deliberately disabled gets enabled. - /hui char barsoff reported CharOverrideList(), which is the list of overridden
setting names - so it always said "barsOff" whatever you toggled. It also
stored typos silently. Now validates against the bar table and reports bars.
And the chat pin is gone. ChatFrame1 inherits EditModeChatFrameSystemTemplate
on 1.15.9 and Edit Mode's preset carries its width and height as well as its
anchor, so pinning it drives the same overrides, the same taint path and the
same silent revert that made the minimap tuck unshippable. Keeping one and
dropping the other would have made the rule decorative. Eight features now.
Also: EnableMouse is a protected function on this build (the comment claimed
otherwise), and .luacheckrc now declares its read_globals soluacheck .is
clean at 0 warnings like the siblings.
The harness grew to 80 checks with one regression per fixed bug, and now fails
on any error the addon's SafeCall contains - a swallowed error was previously a
silent pass. Mutation-tested against three of the fixes.
- Bars force-enabled every proxy bar it was not hiding. Any bar the player had
- Drop the UpdateHotkeys hook - it could never have worked
Two findings from Blizzard's 1.15.9 source, and together they delete code
rather than add it.
Blizzard never calls SetAlpha on an action button's HotKey or Name. It reaches
both font strings through Show, Hide, SetText and SetVertexColor only; the
whole-tree search for SetAlpha on those turns up the raid pullout buttons and
the commentator UI and nothing else. So an alpha set once stays set, and the
correct amount of re-assertion is zero. That is also the real argument for
alpha over Hide - not merely that it dodges a fight, but that it is permanent.
And the hook was inert anyway: Mixin() copies function references onto each
button when it is created, so hooking ActionBarActionButtonMixin.UpdateHotkeys
afterwards reaches no existing button. It would have reported itself as working
in /hui status while doing nothing - which is worse than not having it.
What is left is three events as insurance against a button created after the
first pass. On Era every button exists at load, so they should never fire; they
are cheap and the cost of being wrong is a stray keybind on screen. - Reconcile the design with what the client source actually said, add README
Six factual corrections to DESIGN.md, all from Blizzard's shipping 1.15.9
source rather than inference:- MINIMAP_PING was not removed. The event still exists with a documented
payload; Blizzard moved to RegisterEventCallback and plain RegisterEvent now
rejects it, which is what the reported error actually said. - Status bar text is one native cvar, not two draw-layer changes, and the two
settings collapse into one because both bars share the predicate. - The player health bar needs no hook - lockColor is Blizzard handing it over.
- The minimap feature is the time-of-day dial, and no positioning ships: stock
is already flush top-right and MinimapCluster is an Edit Mode system. - Bar numbering: DragonflightUI's bar4 is Blizzard's bar 5.
- Four siblings park a minimap button, not three - HelloLog was missing.
The Edit Mode question the design flagged as its highest-value unknown is now
answered in both directions: per-bar visibility IS native for bars 2-8, and
keybind/macro text is NOT native anywhere, so one feature got simpler and one
stayed real work.
The TODO list is rewritten around what actually remains, and now says plainly
at the top that none of this has been run in the game yet - the harness covers
logic and end state, not whether a frame looks right.
- MINIMAP_PING was not removed. The event still exists with a documented
- Implement the nine features, plus an offline boot harness
Every API decision here is grounded in Blizzard's own shipping UI source for
this exact build: Gethe/wow-ui-source branch classic_era, version.txt
1.15.9.68808, matching .build.info for wow_classic_era. Several things the
design assumed turned out to be wrong, and the code follows the source
rather than the assumption.
Three features got materially simpler than planned:- StatusBars is a single CVar. StatusTrackingBarMixin:ShouldBarTextBeDisplayed
reads GetCVarBool("xpBarText"), so Blizzard already has the exact switch.
The other two terms in that expression are the mouseover machinery and are
cleared on mouse-out, so they cannot be used. Both bars share the predicate,
which collapses the old profile's two flags into one honest setting. - Player needs no hook at all. UnitFrameHealthBar_Update guards its colour
write withif not statusbar.lockColor, so setting lockColor makes Blizzard
stop resetting it. - Minimap ships no positioning. Stock already anchors TOPRIGHT 0,0 in both
Edit Mode presets, and MinimapCluster is an Edit Mode system: SetPoint is
replaced by an override that writes manager state in our taint context, the
frame is clampedToScreen, and any anchor is reverted on layout save, spec
change and every Edit Mode close. Also, Era loads GameTime_NoCalendar, so
that button is the time-of-day dial and not a calendar.
Two traps worth naming: - Bar numbering. DragonflightUI bound its bar4 to MultiBarLeft; Blizzard calls
that bar 5. These ids follow Blizzard so they match the game's own options
labels, which moves the default from bar4 to bar5 - the same physical bar. - Hiding bar 1 must not Hide() the frame. IsNormalActionBarState() is
return MainActionBar:IsShown()and every multibar is gated on it, so
hiding it drags bars 2-8 down - exactly the case this addon exists for. Bar
1, stance and pet have no native toggle and are made invisible and
non-interactive instead; bars 2-8 drive Blizzard's own settings proxy.
Buttons does NOT collapse to a Blizzard checkbox - the full-tree grep for a
keybind/macro text setting comes back empty, so that feature is real work.
It prefers the button.HotKey/.Name parentKey aliases over the globals.
Darkmode is an explicit allowlist and never an enumeration: four sibling
addons park a Hello*MinimapButton on the Minimap frame, HelloWarrior and
HelloTotems use SetDesaturated as live state, and Masque owns button art. The
buffs and general-UI areas are dropped - the former has no stock target, the
latter was already a no-op upstream.
Tests/test_boot.lua stubs the API, loads all eleven files in TOC order, drives
ADDON_LOADED through PLAYER_ENTERING_WORLD and asserts observable end state:
64 checks. Mutation-tested against the bar-numbering trap and the lockColor
mechanism to confirm it fails when it should.
- StatusBars is a single CVar. StatusTrackingBarMixin:ShouldBarTextBeDisplayed
- Add design document and addon foundation
HelloUI replaces DragonflightUI on Classic Era 1.15.9 with a behaviour-only
de-clutter layer: no texture set, no replacement frames, no reimplemented
layout engine.
Scope was derived by reading the DragonflightUI AceDB profile across both
accounts and 47 characters. AceDB only writes non-default values, so that
file is the complete list of what was actually wanted - about fifteen
entries, every one of which removes something or makes some text
permanently visible. DESIGN.md records the evidence and the nine features
it produced, plus the boundaries against the sibling Hello addons.
Foundation:- Core.lua: ns namespace, guarded RegisterEvent (an unguarded one is what
took DragonflightUI's minimap module down on 1.15.9), per-site error
containment, an out-of-combat work queue for protected frames, and the
module registry. - Config.lua: account-wide settings with a sparse per-character override
list. 47 characters shared one layout and exactly one diverged, so this
is an exception list rather than a profile manager.
- Core.lua: ns namespace, guarded RegisterEvent (an unguarded one is what
This mod has no additional files

