3.3.30
What's new
GSE
3.3.30 (2026-08-24)
Full Changelog Previous Releases
- Merge pull request #2019 from LarryThiessen/fix-2018-pool-reset-styling
#2018 Reset styling, subframe art and frame flags on widget reuse - #2018 Keep the macro-box height meter out of the pool's reach
The reset now sweeps caller-added fields off every frame the widget exposes,
which is right -- but FitMacroEditBoxToContent caches its measuring
FontString as macroEditBox.frame.gseHeightMeter, and that key is
caller-added.
A FontString cannot be destroyed. Swept, the next fit creates another one on
a frame that is reused for the rest of the session, so the box accumulates
one hidden orphan per reuse -- and each one is another entry in the
{frame:GetRegions()} walk that resetForReuse itself runs on every reuse, so
the reset gets slower the longer the session runs. Silent and cumulative.
This is the meter's third home: on the widget table (#2014 strips
post-construction keys), on the frame (this branch strips those too), and
now in a module-local weak-keyed table where neither sweep reaches it. The
frame is the only thing that keeps an entry alive.
Worth knowing generally: with this branch the pool is entitled to remove
anything a caller parks on a widget's frames, so a region cached there is no
longer safe. The comment on the table says so for the next caller.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com
Claude-Session: https://claude.ai/code/session\_01JaKGoo9zcWMDowDLy4cAbX - Merge master into #2018 pool-reset branch
- #2014 reduce round tripping when rendering the editor
- #2018 Reset styling, subframe art and frame flags on widget reuse
Three classes of previous-life state survived the pool's reset:
FontString styling -- labels reused from the block editor kept its fonts
and class colours, so the Config page drew with mixed heading sizes and
colours. Each construction-time FontString's font, colour and
justification is now recorded and restored on reuse. (FontStrings are
detected by having a font API but no CreateFontString: they ARE
ScriptRegions, so filtering on SetScript matches nothing.)
Subframe art and flags -- callers hang textures, child frames and plain
fields directly off the widget's frames, top frame and exposed subframes
alike (rail textures, editBox.GSEMacroEditorColoring...). That is
invisible to the widget-table key sweep: block rail lines bled into the
Config and Notes panels, and a reused multiline box applied macro syntax
colouring to the read-only Notes text. The reset now records every
exposed frame's construction-time key set, regions and children; on
reuse it sweeps caller-added frame fields (engine userdata at [0] kept)
and hides stranger regions and children.
Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com - Merge pull request #2017 from LarryThiessen/fix-2014-widget-pool
#2014 Pool NativeUI widgets instead of abandoning their frames - Merge pull request #2016 from LarryThiessen/fix-2013-fit-batch
#2013 Batch the macro-box fit pushes; hard-hide the box scrollbar - Merge pull request #2015 from LarryThiessen/fix-2010-spellcache-load
#2010 Draw the Spell Cache editor on first open, not at addon load - Merge pull request #2012 from LarryThiessen/feat-2011-tab-menu-order
#2011 Move Macros and GSE Variables to the bottom of the Tab menu - #2013 Wheel always scrolls the block list, wherever the cursor hovers
The focused macro box used to consume the wheel and scroll its own text.
That predates the auto-fit: boxes now size themselves to their content,
so there is nothing left to scroll inside one -- but hovering (or typing
in) a box still changed what the wheel did, which read as jumpy,
inconsistent scrolling. The wheel now always drives the outer block
list; ScrollFocusedMacroEditor is gone.
Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com - #2014 Pool NativeUI widgets instead of abandoning their frames
Release() only hid a widget's frame -- and WoW frames can never be
destroyed -- so every editor redraw abandoned its entire widget set:
+238 widgets per version click, forever. Long sessions accumulated
thousands of dead frames and megabytes of unreclaimable state, and the
collector dragged an ever-growing graph for the rest of the session.
AceGUI pooled widgets for exactly this reason; the rewrite dropped it.
Widgets are plain tables of closures over their own frame, so the SAME
table is reused. UI:Create hands out a banked widget when one exists;
Release banks poolable types. The reset contract, each part of which
covers a real bug found in testing:
* every key added after construction is removed (caller decorations
and wrapped methods must not survive into the next life)
* callbacks/children become fresh tables
* frame scripts recorded at construction are restored on every
subframe the widget exposes
* regions and child frames callers created directly on the widget's
frame are hidden on reuse -- the key sweep cannot see those, and
without this a reused frame keeps rendering its previous life
(header rows bleeding into macro blocks, doubled counters, dead
scrollbars intercepting the mouse wheel)
* a double Release cannot bank the same widget twice
Only the 13 high-churn leaf/container types pool; windows, scroll
frames, tab groups and the tree keep their old behaviour. Kill switch
for diagnostics: /run GSE_NoWidgetPool = true and /reload.
Best merged with #2013: pooled reuse of macro edit boxes is what makes
the scrollbar re-show race there common.
Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com - #2013 Batch the macro-box fit pushes; hard-hide the box scrollbar
Switching versions in the Sequence Editor blocked the client for
0.5-1.2s per click while the editor's own Lua cost 40-70ms. Bisection
(each draw component toggled off independently in a live client) pinned
the difference on one thing: FitMacroEditBoxToContent ran its ancestor
height-push per macro box, mid-draw. Each push re-laid-out every shared
ancestor container -- block list, scroll frame -- re-anchoring all their
children, so a click generated N-boxes x whole-chain SetPoint volume and
the engine's layout invalidation ate the frame.
Fit pushes are now queued and flushed once per frame by a driver, and
each ancestor container is laid out ONCE per flush instead of once per
box (the dedupe is the essential part: deferring the walks without it
just moved the storm one frame later). A queued push whose widget was
released before the flush is skipped. Version clicks drop to 40-70ms
with identical visuals.
Also in this function: the scrollbar hide was a one-time Hide(), but the
scroll template re-Shows the bar whenever the text's scroll range
changes, so the bar could come back. The hide is now permanent for the
widget's life (bar.Show = bar.Hide, applied once).
Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com - #2010 Draw the Spell Cache editor on first open, not at addon load
SpellCache.lua's setup ended with SelectTab on the first cache tab, which
fires GUIDrawSpellCacheEditor and builds two EditBoxes plus labels for
EVERY cached spell -- at load time, for a frame that starts hidden. The
cache only grows (imports merge the exporter's whole cache, the
translator caches every name it resolves, nothing prunes), so long-time
users paid seconds of frozen client inside the on-demand
LoadAddOn("GSE_GUI") that opening any first GSE window triggers.
Measured: 3.8s of a 4.4s blocked frame.
Defer the initial tab draw to the frame's first OnShow: the window pays
its own cost when actually opened, and loading the GUI stops stalling
the client.
Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com - #2011 Move Macros and GSE Variables to the bottom of the Tab menu
They are the whole-block / whole-line cases and the least used entries
while a line is being assembled, yet they sat above Commands. The block
that emits them (all three paths: macro-edit mode's own-line insertions,
the greyed alone-in-the-block case, and the fill-the-block case) moves
verbatim into addNameEntries(), called at the tail of both generator
paths, so the entries land at the bottom with behaviour unchanged.
Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com - #2004 make sure marco edt capability works for macro editing not just sequence blocks.
- Merge pull request #2007 from LarryThiessen/feat-2004-tab-line-builder
#2004 Build the whole macro line from the Tab menu - Merge pull request #2006 from LarryThiessen/feat-2003-macro-block-polish
#2003 Centre the action icon on the macro box and drop its scrollbar - Merge pull request #2005 from LarryThiessen/fix-2002-editor-fill-on-load
#2002 Keep a macro block's height fit inside the block - #2004 Land the first command in the placeholder, not under it
Tabbing on a fresh block put the command on a second line: the pick asks
rowBeforeCaret() whether the current row is empty, and on a new block
that row is the "Need Stuff Here" placeholder -- text, so the command
was prefixed with a newline. The splice then replaces the placeholder
outright, leaving that newline stranded at the top of the block.
The placeholder is not content, so report the row as empty.
Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com - #2004 Allow the Blizzard menu globals the Tab builder uses
luacheck reported 28 undefined globals in QoL.lua: the line builder
reaches past MenuUtil (already allowed) to MenuResponse for its pick
return values, and to Menu / MenuVariants / AnchorUtil for the anchored
open, plus GameTooltip_AddNormalLine for the greyed entries' tooltips.
Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com - #2004 Build the whole macro line from the Tab menu
Tab in a macro block opened a flat list of spellbook spells and GSE
variables and pasted a name at the caret. It saved typing one word; the
author still wrote every command, bracket, conditional and reset= by
hand.
Tab now opens a line builder. The menu stays open while the line is
assembled, each pick lands in its canonical place -- command,
conditionals, reset=, spells -- whatever order they are picked in, and
entries that would produce an invalid line are greyed with a tooltip
saying why.
Undo Last / Boiler Plates / Macros / GSE Variables / Commands /
Conditionals / Reset / Spells / ; / , nil
Conditionals accumulate into one always-closed bracket group, rewritten
in place ([a] -> [a,b]), with same-family collapse (spec:1 + spec:2 ->
spec:1/2) and duplicate picks deduped; [] closes the run. Reset is
offered only on a /castsequence line, slash-collapses its values and
keeps a single number. ";" opens a fresh clause with its own conditionals
and reset=, and ", nil" is offered only at the end of a clause that has
its own reset= and ends with a spell. Macros and GSE Variables stay
mutually exclusive with macro text, in both directions.
Two things make it robust rather than clever:
* The text is the single source of truth. The clause state is
re-derived from the live text before every conditional and reset
pick, so picks that end a clause -- or the author typing between
picks -- cannot leave stale offsets behind. Every read decodes the
box first, so a repaint mid-build cannot shift offsets or blind a
gate, and edits splice via SetText at tracked offsets rather than
trusting the caret of an unfocused box.
* Nothing depends on the menu's lifecycle. Blizzard menus release and
re-acquire during submenu navigation, and MenuResponse.Refresh does
not re-run the root generator, so any pick that changes a gate closes
and reopens the menu instead. The menu is opened through the manager
with an explicit anchor on the macro box, so those reopens do not
walk it across the screen the way cursor anchoring did.
Editor.lua defers its focus-loss commit repaint while a session is live,
so it cannot recolour the box mid-build; it runs on the next real focus
loss once the menu is gone.
Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com - #2003 Centre the action icon on the macro box and drop its scrollbar
Two leftovers from before the macro command box sized itself to its
content.
The action icon sat at a fixed position in its slot, so it drifted out
of line as the box grew and shrank. Anchor its CENTER to the box's LEFT
edge instead: a live anchor follows every height change with no
per-resize maths. The icon slot's DoLayout is wrapped so a re-layout
cannot snap the icon back to its static slot position.
The box now grows to fit its content, so its scrollbar never has
anything to scroll -- hide it. A block is capped at 255 characters and
cannot realistically reach the 24-row cap.
Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com - #2002 Keep a macro block's height fit inside the block
FitMacroEditBoxToContent pushes the height delta of a re-fitted macro
command box up through its fixed-height ancestors, but the walk ran to
the top of the widget tree. Only the containers inside a block should
absorb that delta; above the block panel sit shared containers -- the
block list, the layout container, the scroll frame -- and every block's
fit on load subtracted its own delta from those same containers again.
On a multi-action sequence that collapsed the list area by roughly the
sum of the blocks' deltas, so the editor no longer filled the window and
the last block was clipped.
Stop resizing once the walk reaches the first auto-height ancestor: that
container recomputes its height from its children, and everything above
it follows normally. Past that boundary we only re-lay out.
Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com
This mod has no additional files

