v0.1.0-classic
What's new
HelloGear
v0.1.0 (2026-07-25)
Full Changelog Previous Releases
- Add release packaging
Same shape as the sibling addons: a tag matching v* triggers the BigWigs
packager, which builds the zip, generates a changelog from the commits
since the last tag, creates a GitHub Release, and uploads to CurseForge
when CF_API_KEY is present in repo secrets.
The workflow checks the tag against the TOC's version before packaging.
The TOC carries a literal version rather than @project-version@ so a copy
loaded straight from a checkout shows a real number in game, which means
it can drift from the tag - better to fail than to publish a zip whose
in-game version is wrong.
.pkgmeta keeps the dev-only material out of the zip: the headless test
harnesses, the lint config, the docs, and the CurseForge project image.
Icon.png ships because the TOC's IconTexture points at it, and LICENSE
ships because the MIT terms ask for the notice to travel with copies.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com - Add the CurseForge project ID
Placed as the siblings do, after Category.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com - Group the panel buttons by what they act on
Undress moves up beside Equip, where it belongs: both act on the character
right now. That leaves three pairs, each row doing one kind of thing -
Equip/Undress on the character, Save/New set on sets, From/To bank on the
bank - instead of an arbitrary split between a top row and a footer.
New set comes up from the footer to fill the second row, which keeps the
row count and so leaves the list exactly the height it was.
The buttons also fit now: two at 83 plus a gap came to 170 in a 164-wide
column and overhung into the scrollbar. Halving the column gives 80.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com - Add an undress button
Takes everything off into your bags. The swap engine already knows how to
empty a slot, so this is all of them at once and inherits the bag-space
check and the retry loop rather than reimplementing either - gear stays on
if there's nowhere to put it, and it says so.
Sits beside New set in the footer, which shortens to fit; its tooltip
carries what the longer label used to say. Also /hg undress.
Deliberately not undoable. Recording it as a set's restore would mean
silently overwriting one, which is worse than making the user equip a set
to dress again.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com - Make every icon searchable by name
Your dump came back as 1106 bare file IDs, so IconNames.lua maps them,
generated from the community listfile (verified-listfile.csv) and filtered
to exactly those - 49KB rather than the megabyte the full retail listfile
would cost for no gain. Load is a table constructor; nothing is built until
the picker first opens.
Matching now happens on a form with every separator stripped. Names arrive
from three places and none of them agree on spelling -
spell_shadow_shadowbolt from the table, Chromatic Boots from an item,
Shadow Bolt from a spell - and nobody types them consistently either, so
"shadow bolt", "shadow_bolt" and "SHADOW BOLT" all find the same icons.
Runtime naming from gear and spells stays: it needs no shipped data and
covers whatever a later patch adds before the table is regenerated.
test_iconsearch.lua runs the search key against the real shipped table.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com - Add /hg dumpicons to capture the client's icon list
Groundwork for making every icon searchable. There is no API turning a
file ID into a name, so the mapping has to be generated offline from the
community listfile - and filtered to the icons this client actually has,
or it means shipping tens of thousands of retail entries for a Classic
addon.
/hg dumpicons writes the raw list to saved variables; a reload puts it on
disk where it can be read and turned into a table. Needed once, and again
only if a patch adds icons - file IDs are stable, so an out-of-date table
misses new icons rather than going wrong.
The picker already looks for ns.IconNames when naming an icon, so the
generated file drops straight in with no further wiring.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com - Widen icon search to everything with a name
Search covered twenty icons because this client reports its macro icons as
bare file IDs, which carry no name to match against - so only the set's own
gear, named from the items themselves, was searchable.
Everything else whose icon can be named now is: the rest of your gear,
whatever's in your bags and the bank when it's open, and every spell you
know. An icon shared with one of those is searchable by that thing's name,
which is what you'd type looking for it anyway. The spellbook call differs
across clients and neither form is guaranteed, so a miss there just means
fewer names.
The rest stay browsable but unsearchable. Covering them would need a file
ID to name database shipped with the addon; the tooltip says as much
rather than leaving the ceiling unexplained.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com - Make icon search work, and tidy the picker's placement
Search asked the wrong client call first. There are two: the indexed one
yields texture paths, whose last segment is a name, and the table-filling
one yields bare file IDs, which carry nothing to match against. The
table-filling call was tried first and returned successfully, so the
indexed one was never reached and almost nothing had a name to search.
Now the indexed call goes first and is only kept if it really does hand
back strings.
The header says what's actually searchable rather than leaving a box that
quietly covers a fraction of the grid and reads as broken - the count of
icons and, when they differ, how many have names. If none do, it says so
and the box is disabled. SetEnabled also becomes Enable/Disable, which are
the methods that certainly exist on an EditBox here.
The picker sat further from the options panel than intended: both frames
draw their borders inside their bounds, so the visible gap was the offset
plus both insets. Offset accounts for that now.
Closing the character sheet closes the picker, as does dismissing the
options popout it hangs off - it belonged to that popout, and leaving it
floating alone was debris.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com - Show sets with gear you don't have in red
A set whose items aren't on you or in your bags now reads red in both the
panel list and the quick menu, and its tooltip marks the offending slots
rather than making you compare the list against your bags by eye.
Anything the bank is holding is marked (bank) instead of (missing). Both
are red - either way it isn't going on right now - but one of them tells
you where to go. That distinction only exists while the bank window is
open, since the client won't report its contents otherwise, and the
tooltip degrades to "missing" when it can't ask.
The lookup reduces to an item-and-suffix key so a set's slots are table
lookups rather than a scan, and both lists build the inventory index once
for the whole list instead of once per set. Eight cases cover it: worn and
carried gear not counting, a re-enchant not counting, a different random
suffix counting, and the bank distinction appearing and disappearing with
the window.
Also fixes the icon picker opening on top of the options panel that
launched it - it sits beside it now - and drops "Use a set item", which
the set's own icons coming first already covers.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com - Move sets to and from the bank
The client only reports bank contents while the bank window is open, which
shapes the feature: this can't be something equipping reaches for on its
own, since the moment you want a set is the moment you're nowhere near a
bank. So it's two explicit actions - buttons on the panel and /hg bank
get|put - both disabled with an explanation unless the window is up.
From bank moves the set's gear into your bags, skipping anything already
on you or already carried. To bank moves it out of your bags and leaves
worn gear alone; stripping a set off to put it away is what unequipping is
for, and doing it implicitly would be a nasty surprise.
The move loop follows the swap engine: recompute from the world each pass,
move what can be moved, wait for the locks, go again. It gives up if the
bank window closes underneath it.
A swap that can't find something now says how many of the missing pieces
are in the bank, when the bank is open to be asked.
Four scenarios cover it against a simulated bank - withdrawing, skipping
what you already have, depositing without stripping worn gear, and the
window being shut.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com - Drop the key bindings
Removes Bindings.xml, the BINDING_NAME globals, /hg bind and the
per-character table behind it, along with the set rename and delete paths
that kept that table in step.
HelloGear.ToggleMenu goes with them - it existed only as the binding's
entry point, and the minimap button and a bare /hg both already open the
menu. The macro API is untouched, so a macro on an action bar still gets
you a keypress if you want one.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com - Add an icon picker for sets
Sets could only take the icon suggested from their own gear. The gear
button's options now open a picker: an 8x7 grid, mouse wheel or slider to
move through it, click to choose.
The set's own item icons come first. Nine times in ten the icon you want
is something the set puts on, and finding it in a grid of two thousand
otherwise means scrolling for it. The client's macro icon list follows,
fetched through whichever call this client has - modern fills a table,
older ones hand back one at a time, and neither is guaranteed. If neither
exists the picker still offers the set's own icons rather than failing.
The grid opens on the row holding the set's current icon, which is marked,
so it starts where the answer probably is. "Use a set item" keeps the old
suggest-from-gear behaviour.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com - Stop Save from discarding slot edits
Save replaces the set with whatever the character is wearing. That is what
it is for, but it sat one click away from the slot editor, so editing a
set and then pressing Save threw away every choice just made - which reads
exactly as the edits not having been saved at all.
The edits were already stored: slot changes are written into the set as
they happen, so there was nothing to save in the first place. Save is now
disabled while a set is being edited, labelled "Saved", and its tooltip
says why. The status line says changes save as you go.
Test covers the replace-not-merge behaviour the guard depends on: a
hand-picked slot is overwritten from the character, unmanaged slots are
added, and a deliberate empty is dropped.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com - Bake in the calibrated panel gap
/hg dock -5 against a gap of 6 puts the right value at 1: the borders read
as adjacent rather than joined, without the panel looking flung off to the
side.
The saved nudge is cleared once on load. It existed to calibrate this by
hand, and now that the measured value is the default, leaving it in place
would apply on top and put the panel back where it started.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com - Stand the panel beside the character sheet, not inside it
Three changes, all pulling the same way.
The fill goes back to the dark it had before the header-band colour; that
colour read as bulk rather than as background.
The panel stops being tucked under the character frame's right border and
sits a short gap clear of it, drawing in front rather than behind. Sharing
a seam was meant to read as integration and instead read as one oversized
frame. It is its own window standing beside the character sheet now, the
way the guild information window does.
The frame is no longer inflated by the border's inset, which was there to
line its drawn edges up with the character frame's artwork - pointless
once the two are deliberately separate, and it was making the panel bigger
than the space it occupies. Top and bottom still track the artwork's, so
the height matches the character sheet.
Also clears out comments left behind by the borrowed-artwork attempts,
which described anchoring that no longer exists.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com - Go back to the addon's own chrome for the panel
Matching the character frame's border exactly was attempted at length and
made things worse each time. Drawing its quadrants whole carried the
paperdoll's slot recesses and the tab hardware into the middle of the
panel. Slicing clean strips sampled the wrong half of the texture file,
because those regions carry texcoords of their own that SetTexCoord
discards. Drawing the edge by hand from measured colours lost the bevel.
Back to ns.ApplyChrome - the same border the quick menu and the flyouts
use - keeping the character frame's header-band colour as the fill, which
was asked for separately and isn't part of what regressed. It reads as a
sibling of the character frame rather than a continuation of it, and it
doesn't degrade.
Everything the detour did establish is kept: the measured position and
size constants, the artwork padding, the scrollbar room, and the interior
layout, which is unchanged - the panel is inflated by the border's inset
so PANEL_WIDTH stays the width you actually see.
Removes the slicing constants and the chrome diagnostic that described
pieces which no longer exist.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com - Draw the panel's edge instead of slicing the frame's artwork
Reusing the character frame's own textures never worked. Whole quadrants
brought the paperdoll's slot recesses and the tab hardware with them.
Slicing clean strips kept sampling the wrong part of the file - the
right-hand quadrants are 128 wide drawn from the left half of a 256-wide
texture, so those regions carry texcoords of their own and SetTexCoord
discards that mapping. Composing with them still didn't land it.
Measuring what the frames look like settled it instead. The character
frame and the social window edge identically: a bright highlight outside
at RGB ~141, a dark line immediately inside at ~20, over a mid tone that
is the same colour as the header band the panel is already filled with.
Three flat colours, so the panel draws them - exact, with no texture to
sample wrongly and nothing left to drift.
Removes the slicing machinery, its border-width tuning command, and the
tests that covered it. /hg dock art still reports the frame's regions,
now including their texcoords.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com - Slice inside the region's own texcoords
This is why the border never came out right. Blizzard's right-hand frame
quadrants are 128-wide regions drawn from the left half of a 256-wide
file, so each region already carries texcoords of its own. Calling
SetTexCoord with coordinates computed in whole-file space threw that
mapping away and sampled the wrong half of the texture - which is exactly
why drawing the quadrants whole looked right, and every sliced version
since has looked wrong however the coordinates were adjusted.
The slice now maps its fraction of the region onto the rectangle the
region is actually showing. Every attempt to fix this by moving the sample- the x range, then the depth - was chasing the symptom.
/hg dock art reports each region's texcoords, and two more cases cover
composition and that a slice stays inside the half its region shows.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com
- the x range, then the depth - was chasing the symptom.
- Sample the border where it actually has a bevel
/hg dock chrome confirmed all six pieces are shown and correctly placed,
so the slices were never missing - they were sampling the wrong part of
the artwork.
The right border isn't uniform down its length. At depth 60, where this
sampled, it's flat and dark; from about depth 120 down it carries the
bevel that ends in the bright outer line at RGB 140,139,137. Verified the
same at 125, 245 and 365, so the sample moves to 250. That dull dark band
along the panel's edge was the frame's own artwork, just an atypical
stretch of it.
The fill also came back lighter than intended: 0.227 in, 71 on screen,
against the header's 58. Scaled down to land on the header's rendered
value rather than its nominal one.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com - Use the character frame's header band as the panel background
The fill was a near-black of my own choosing. Sampling the character
frame's header - the strip carrying the level and class - gives RGB
58,53,49: warmer and considerably lighter. That's what the panel uses now.
The sliced border still isn't showing. Its pieces are either absent or
landing somewhere unhelpful, and from a screenshot those two look
identical, so /hg dock chrome reports each piece's texture, whether it's
shown, and the rectangle it occupies - against the panel's own rectangle.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com - Measure the artwork's edges from pixels instead of estimating
Sampling the screenshot's pixels directly rather than eyeballing it: every
constant describing where the artwork's visible edges sit was wrong, and
each error had a visible symptom.
At y=300 the character frame's bright edge is at x=443, the world shows
through 444-453, and the panel's fill starts at 456 - a real gap. The
artwork's right edge is 348, not 359, so the panel sat ten pixels clear of
the frame and the right border strip sampled transparent padding, which is
why only a single pixel of highlight drew instead of a bevel.
At x=300 the character frame's top edge is at y=150 while the panel's is
at 144: the artwork has five rows of transparent padding above its top
border. The top and bottom strips were sampling from row zero, catching
the padding and drawing nothing.
At the bottom the frame's edge is y=681 against the panel's 656. The tabs
overlap upward INTO the artwork, so its bottom is 11 below the tab row's
top, not 10 above it - the sign was inverted, leaving the panel 21 short.
Geometry tests updated to the measured values.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com - Fix the right border slice smearing across the panel
The right-edge strip was anchored TOPRIGHT and BOTTOMRIGHT. Both points
are on the same edge, so they fix its height and say nothing about its
width - the texture fell back to its natural 128px and drew a stretched
border smear a third of the way across the panel, which is the light band
in the screenshot. The actual edges got no border at all.
SetWidth on that one texture. The other five pieces anchor opposite
corners or set an explicit size, so none of them had the same hole.
The slice arithmetic is now Panel.TexCoords and covered by five cases in
the geometry test: the two strips this uses, that their coordinates land
inside the texture at all - sampling outside is how the paperdoll's slot
recesses got in - and that depth in a bottom quadrant is measured from the
artwork's top rather than the quadrant's.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com - Slice clean border strips instead of whole quadrants
Drawing the character frame's quadrants wholesale got the style right but
brought their contents along: the paperdoll's slot recesses, and along the
bottom the hardware the character tabs bolt onto, which lands orphaned in
the middle of a panel that has no tabs.
Each edge is now a clean strip cut from the same textures with SetTexCoord- runs of border with nothing attached to them - and the bottom reuses the
top border flipped, the top being the one horizontal edge with no hardware
on it. The bottom-right corner mirrors the top-right for the same reason.
Every piece anchors to the panel's own edges, which also drops the whole
question of whether the panel and the artwork agree about where the
frame's corner is: they no longer have to.
/hg dock border <n> re-slices at a different border thickness, so the one
remaining guess - 11px - can be corrected in game rather than over another
round of screenshots.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com
- runs of border with nothing attached to them - and the bottom reuses the
- Anchor the interior fill to the artwork, not the panel
Zooming into the screenshot showed the fill and the borrowed artwork are
not quite in the same place: a band of the paperdoll's slot recesses shows
between the fill's edge and the border, on the right and along the bottom.
The two were positioned from different things - the fill from the panel's
bounds, the artwork from the derived visible corner - so any error in that
corner separates them, and the gap fills with whatever the paperdoll had
there. The fill is now placed from the artwork's own origin using the
artwork's visible extents, so the two move together and the seam between
them cannot open up regardless of where the panel itself lands.
/hg dock also reports PaperDollFrame's origin and the computed artwork
offset now, which is the number this all turns on.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com - Cover the paperdoll detail the borrowed artwork brings with it
Drawing the character frame's own textures gets the border exactly right,
but it also brings the paperdoll's interior along - the empty slot
recesses show as a column of boxes down the panel's right side and another
row across its bottom.
The border is the only part worth borrowing, so everything inside it is
covered with a flat fill. The left inset is zero: that edge is the
character frame's own border, drawn by the character frame itself, so the
fill runs right up to the seam.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com - Draw the character frame's own artwork on the panel
/hg dock art settled what the border actually is: four texture quadrants
laid out 384x512 on PaperDollFrame, the classic corner-piece frame. No
reusable template exists for it, which is why both look-alikes tried here- the tan UI-DialogBox border, then the dark nine-slice - were visibly not
it however they were sized or inset.
So the panel stops approximating and draws those same four textures,
read off PaperDollFrame at runtime rather than hardcoded, shifted right so
their right border lands on the panel's right edge. The rest runs off to
the left behind the character frame, which the panel already draws
underneath. What shows is the character frame's own border, corners and
interior simply continuing - there is no seam to match because there
isn't one.
That also removes the inflate-by-the-border-inset arithmetic from the last
two rounds: the borrowed artwork carries the same transparent padding the
character frame's does, so the panel's bounds are just the artwork's
bounds again.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com
- the tan UI-DialogBox border, then the dark nine-slice - were visibly not
- Take the panel's top from the frame, not the close button
The panel was sticking out above the character frame. Using the close
button for the top was wrong: the artwork is flush with CharacterFrame's
top, and measuring the close button pushed the panel up a dozen pixels -
which also broke the height, since the artwork is 424 tall and that only
comes out right when the top is the frame's own.
The close button is still right for the horizontal, where /hg dock's 359
matches the frame's real edge. Only the vertical reverts.
/hg dock art now reports each region's size and anchor alongside its
texture, since the character frame's artwork turns out to be four
textures on PaperDollFrame rather than anything on CharacterFrame - the
panel's border can be built from those same pieces once their layout is
known.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com - Tuck the panel under the character frame's border
Two fixes for "smack bang next to it" and "too short", both from the same
cause as the last round: the panel's frame was being positioned correctly
while its drawn border sat 11px inside those bounds.
The frame is now inflated by that inset on every side, so the drawn edges
land on the character frame's visible edges instead of inside them - which
is what made the panel look short at top and bottom.
Horizontally it goes one border width further, tucking the panel's left
edge under the character frame's right border, and the panel now draws
behind the character frame rather than in front. The seam between the
two is therefore the character frame's own artwork, which matches by
definition, instead of two borders in slightly different styles butted up
against each other.
The panel's other three borders still aren't the character frame's, so
/hg dock art dumps that frame's own texture regions - the panel can then
be built from the same pieces rather than a look-alike picked by eye.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com - Correct the border's drawn inset so the panel docks flush
The position arithmetic was already right: /hg dock computes the visible
corner at 359, and calibrating against the screenshot that is exactly
where the character frame's right edge falls. The gap was the other half
of the sum. The panel's frame sat at 355 but its border drew from ~366,
so the two drawn edges were 7px apart however well the frame was placed.
The border template draws its edge ~11px inside the frame's bounds, the
same as the backdrop it replaced - not the ~4 assumed when the nine-slice
path went in. One number, measured off the screenshot rather than guessed.
Worth recording why this took two passes: a correct frame position with
the wrong overlap is visually identical to a wrong frame position, and I
kept re-deriving the position each time.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com - Take the panel's position from the close button
/hg dock reported "no nine-slice", so the anchor added last commit never
applied and the frame fell through to the slot-column derivation - which
is wrong. Measured off the screenshot, the visible right edge is around
352, but mirroring the left column's 21px margin onto the right derives
364. The margins simply aren't symmetric; the right is nearer 9.
The close button is a better reference and was available all along:
Blizzard pins it to the artwork's top-right corner, so its own right and
top edges sit a few pixels inside the frame's. That fixes the horizontal
gap and the panel's top, which was sitting slightly above the character
frame for the same reason - CharacterFrame's top is not the artwork's.
The nine-slice path stays first in line for a client that has one, the
column derivation drops to last, and /hg dock reports which is in use
along with the corner it worked out.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com - Anchor the panel to the character frame's nine-slice
The screenshot showed the panel still a few pixels clear of the frame and
noticeably short at the bottom. Both come from the same mistake: every
attempt so far reconstructed the visible frame's edges by measuring around
CharacterFrame, whose own bounds sit in dead space.
There is no need to reconstruct anything. The character frame is modern
chrome now, so its border is a nine-slice, and CharacterFrame.NineSlice's
bounds are the visible frame. Anchoring the panel's TOPLEFT and BOTTOMLEFT
to its right edge matches all three edges at once - flush horizontally,
and exactly as tall - with no arithmetic and no constants.
The measured path is kept as a fallback for chrome without a nine-slice,
and /hg dock now reports which one is in use.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com - Use the modern frame chrome, and keep the scrollbar inside the panel
A screenshot settled what several rounds of guessing hadn't. Two things
were wrong, and the first I had backwards.
1.15.9 didn't only move Era's code onto the shared UI, it moved the look:
the character frame is the dark nine-slice chrome now, not the old tan
parchment. UI-DialogBox-Border was never going to match it at any edge
size - it's the wrong frame entirely. Panels now use the same
DialogBorderDarkTemplate nine-slice, probed for once at load, with the
backdrop kept only as a fallback. ns.ApplyChrome is the one place that
decides, and it also carries the overlap the border needs, which is much
smaller for the nine-slice than for the backdrop.
UIPanelScrollFrameTemplate anchors its scrollbar outside the scroll
frame's right edge, so sizing a scroll frame to the full content width put
the bar on the border or past it, outside the panel. Every list now gives
up SCROLLBAR_ROOM for it and hides the bar when the list fits.
The panel also spans the artwork's full height rather than insetting 12px
top and bottom, so its edges line up with the character frame's.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com - Match the character frame's styling, and its height
The panel's corners were squaring off because UI-DialogBox-Border was
being drawn at edgeSize 16. That texture is cut for a 32px edge; below it
the corner pieces get squashed and lose the chamfered "dulled point" the
rest of the Classic UI has, which is enough to read as a different UI.
Now drawn at Blizzard's own numbers - edgeSize 32, insets 11/12 - shared
as ns.BACKDROP so the menu, the slot flyout and the options popout can't
drift apart from it. Their interiors move in to clear the thicker border,
and the docked panel widens to 214 to keep Equip and Save side by side.
Height now comes from the frame rather than a constant. CharacterFrame
runs past its artwork at the bottom too, to leave room for the tab row,
and the tabs sit across the artwork's bottom edge - so they're what gets
measured. The panel anchors top and bottom and comes out exactly as tall
as the character frame, tracking it instead of guessing.
Panel.ComputeArtBottom is separated out like the horizontal fit, with six
cases covering the stock tab row, a moved frame, a missing tab row, and
the fallback bounds. /hg dock reports the vertical numbers too.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com - Dock the panel from the measured artwork edge
/hg dock on a 1.15.9 client reports CharacterFrame spanning 0..384 with
the slot columns at 21..343, so the artwork ends around 364 and the frame
carries ~20px of dead space past it - not the ~46 I had assumed from the
vanilla UIPanelFrame convention. The fallback constant is corrected to
match, and the geometry test now runs against those real numbers instead
of my guess.
Two fixes beyond the constant:
The panel anchored its top to a slot button, which is most of the way down
the frame, so it hung far below the character frame's bottom. Horizontal
position still comes from the artwork's edge, but the anchor is now
CharacterFrame's TOPLEFT with a measured x.
Docking flush means overlapping: the backdrop's edge texture carries ~5px
of transparent padding, so the panel's frame has to sit that much inside
the artwork for the drawn edges to meet. That is now explicit rather than
folded into a magic number.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com - Fix nil GetItemCooldown when opening a slot flyout
1.15.9 removed the bare GetItemCooldown global, and C_Item carries no
replacement in this build, so ns.API.GetItemCooldown's fallback chain
resolved to nil. Nothing noticed until a flyout row tried to draw a
cooldown swirl, which is any left-click on a slot while editing a set.
Cooldowns are now read from where the item actually is - the bag slot it
sits in, or the inventory slot it's worn in - which is information the
caller always has and which uses calls that exist. The flyout threads the
item's location through from the bag scan for that. An item the set points
at but you aren't carrying simply has no cooldown, rather than an error.
Two things to stop this recurring: ns.API no longer holds an entry that
can't be satisfied on this client, and a login-time check reports any that
turn up nil rather than waiting for a click to find them. Regression test
covers cooldowns off a bag slot, off a worn slot, and off no location.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com - Edit set slots through the item flyout; add a dock nudge
Slot editing was a single click cycling through worn / clear / ignore,
which made you tour states you didn't want to reach the one you did, and
gave no way to choose an item other than the one already on.
Left-click on a slot now opens the same flyout used for swapping, in
assign mode: it lists everything you're carrying that fits, leads with a
"No item" row for a set that deliberately strips the slot, ticks whatever
the set currently holds, and writes the choice into the set rather than
onto the character. Gear the set points at that isn't carried - banked, or
gone - is listed too, so it can be seen and replaced.
Right-click takes the slot in or out of the set. Keeping that separate
matters because "the set clears this slot" and "the set ignores this
slot" are different things.
Sets:CycleSlot is replaced by SetSlot and ToggleSlot, with the tests
rewritten to match.
Also fixes a real bug the linter caught in the new flyout code:local start, duration = baseID and API.GetItemCooldown(baseID)yields only the
first return value, so no cooldown swirl would ever have drawn.
The panel still isn't docking flush. Both attempts at deriving the
artwork's edge have missed, so /hg dock now reports what the addon
actually measures and /hg dock <pixels> shifts it, saved per account.
Meanwhile the panel hangs off the right slot column - the one reference
point that's known good, since the button anchored to it lands correctly.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com - Fit the character panel and button to the frame's artwork
CharacterFrame is a vanilla UIPanelFrame: 384x512 of frame holding
338x424 of artwork. Its right edge is therefore ~46px outside the frame
you can see, which is why the panel sat detached from it and why the
button, anchored off the close button's own bounds, had no margin.
Neither is anchored to the frame's bounds any more. Both are placed from
the paperdoll's slot columns, which sit symmetrically inside the artwork:
the left column's margin is also the right column's, so the right
column's edge plus that margin gives the artwork's edge. The panel docks
flush to it and the button sits above the top slot of the right-hand
column, sharing its right edge and so inheriting the frame's own margin.
Measured rather than hardcoded, so it also fits a frame another addon has
resized, and re-measured on every PaperDollFrame show rather than once at
login. A clamp falls back to the stock 46 if the numbers come out absurd.
The arithmetic is Panel.ComputeArtInset, kept free of frame lookups so
Tests/test_geometry.lua can exercise it: stock layout, moved frame,
artwork that fills its frame, narrower padding, and the fallback cases.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com - Replace the sidebar tab with a button inside the character frame
The spellbook sidebar-tab artwork is drawn to key into that frame's
specific edge, so it looked wrong hanging off the character frame. In its
place: a small action-button-styled toggle (icon plus the UI-Quickslot2
ring at Blizzard's 1.83x ratio) sitting inside the frame, under the close
button.
The panel now hangs off that button rather than off the frame, so it
reads as belonging to the control that opens it, and nothing measures
against CharacterFrame's own bounds - which sit well outside the visible
artwork.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com - Move set management onto the character sheet
Replaces the standalone manager window with a panel docked to the
character frame, opened by a sidebar tab, and makes the paperdoll itself
the set editor.
Docked outside rather than inside the frame: Classic's character frame
has no right-hand column to borrow, so an inside panel would cover the
model and the slot buttons you need to click while editing. The tab
anchors off CharacterFrameCloseButton, which sits at the top-right of the
artwork and so tracks it - CharacterFrame's own right edge is a long way
outside the visible frame - and the panel anchors off the tab.
Slot editing needs the overlays to own the mouse. A paperdoll slot's
default handler picks the item up before any hook of ours sees the click,
so there is nothing to cancel; instead each slot's overlay button takes
the mouse only while a modifier is held or a set is being edited. The
set's item is drawn on the overlay rather than by retexturing the slot,
which PaperDollItemSlotButton_Update would undo on the next inventory
event.
The panel follows PaperDollFrame rather than its own parent, since
switching to Reputation leaves CharacterFrame shown. That hide is not
recorded as the user closing the panel.
SlotMenus.lua becomes Paperdoll.lua and owns everything attached to the
slot buttons; UI.lua becomes CharacterPanel.lua. Slot state and the edit
cycle moved into Sets.lua so they can be tested headlessly - three new
cases cover the cycle, bare slots, and the worn/stored distinction.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com - Initial commit: gear set manager for Classic Era
Replaces ItemRack, which throws on client 1.15.9: it calls ShowHelm() and
ShowCloak() unguarded in ItemRackEquip.lua, and both globals were removed
when Era moved onto the modern shared UI codebase.
Scope is sets, swapping and the character-sheet slot menus. ItemRack's
auto-swap event rules and cooldown queues are deliberately left out.
Notable design points:- Gear IDs are itemID:enchant:suffix:unique. The link's level field is
dropped, which is what forced ItemRack to rewrite stored IDs on every
ding. suffixID is never ignored, so random-suffix drops can't satisfy
each other. - The swap engine only moves an item when it can find a strictly better
match than what the slot already holds, so a set saved before an item
was re-enchanted stops reporting that item missing. - Swaps run in passes that re-read the world each time rather than
scripting a move order, driven by ITEM_LOCK_CHANGED plus a ticker -
events alone deadlock, since a pass that can't move anything produces
no events to wake up on. - The ItemRack import snapshots ItemRackUser into our own saved
variables on first sight, so it keeps working once ItemRack is gone.
It never writes to ItemRack's data.
Tests/ holds headless harnesses (not in the TOC). The import is verified
against real ItemRack SavedVariables files; the swap engine runs against
a simulated character with worn slots, bags and an item cursor.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com
- Gear IDs are itemID:enchant:suffix:unique. The link's level field is
This mod has no additional files

