Releases: TimothyLuke/GSE-Advanced-Macro-Compiler
Releases · TimothyLuke/GSE-Advanced-Macro-Compiler
Release list
3.3.32
GSE
3.3.32 (2026-09-02)
Full Changelog Previous Releases
- Merge pull request #2046 from LarryThiessen/fix-2044-showtooltip-metatable
#2044 ApplyShowTooltipToAction must rawget: the Actions array carries the path metatable - #2044 ApplyShowTooltipToAction must rawget: the Actions array carries the path metatable
Saving from the editor errored on every sequence:
GSE/API/Statics.lua:504: bad argument #1 to 'ipairs' (table expected, got string)
... ApplyShowTooltipToAction <- SanitizeSequenceEditorMarkup <- ReplaceSequence
SanitizeSequenceEditorMarkup walks every table in the sequence and now calls
ApplyShowTooltipToAction on each one. The editor sets
Statics.TableMetadataFunction on Versions[n].Actions, and that __index only
understands table (path) keys, so the plainaction.Typelookup on the
Actions array itself lands in ipairs("Type"). Editor.lua already rawgets
past this metatable for the same reason.
rawget Type and the macro bodies. Spec added: an Actions array carrying
the metatable returns false without erroring (fails on the previous code
under a 5.1-strict ipairs with the exact in-game message).
Co-Authored-By: Claude Fable 5.1 noreply@anthropic.com - #2045 Record the origin key a sequence is born with, and freeze it
- #2044
#showtooltipis ignored in practive in GSE and should not be stored as part of the macro block chewing up character count - Merge pull request #2042 from LarryThiessen/fix-2040-new-sequence-versions
#2040 Build version children for any loaded sequence, not only path-matched ones - Merge pull request #2043 from LarryThiessen/fix-2041-class-move-duplicate
#2041 Remove the old record when a sequence changes class or spec - #2041 Remove the old record when a sequence changes class or spec
The Specialization/Class ID dropdown reassigns editframe.ClassID in place,
and the save enqueues a Replace under that NEW classid. Nothing removed the
record from the old one, so GSESequences[old][name] and GSE.Library[old][name]
survived and loaded again -- the sequence came back under both branches on
the next reload, which looks like something is duplicating sequences.
A rename already moves its old key via renamesequence; a class move had no
equivalent. Record the class a sequence was loaded from and, when a save
changes it, drop the old record from both stores. Keyed on the original
name and placed before the rename branch (which returns early) so a rename
and a move in the same save are both covered.
Co-Authored-By: Claude Opus 5 noreply@anthropic.com - #2040 Build version children for any loaded sequence, not only path-matched ones
versionsWanted decided whether to build a node's version children from the
tree's expanded/selected state at build time. GUICreateNewSequence adds the
record to GSE.Library and then calls ManageTree(), but the new node is
neither expanded nor selected yet -- its SelectByValue runs after the build
-- so no path matched and the node came up with Configuration and New
Version and nothing between them until the editor was closed and reopened.
The decompress is the expensive half and stays gated on wantVersions.
Assembling children for a record already in the Library is a few table
inserts, so gate the load, not the children.
Co-Authored-By: Claude Opus 5 noreply@anthropic.com
3.3.31
GSE
3.3.31 (2026-09-01)
Full Changelog Previous Releases
- Merge branch 'master' of https://github.com/TimothyLuke/GSE-Advanced-Macro-Compiler
- @2023 fix editor drift where it has moved away from the source of truth (storage.lua) and is trying ti implement things that simply dont exist. The messages now point to the actual settings which match what is present on gse.tools
- Merge pull request #2039 from LarryThiessen/fix-2038-variable-implementation-link
#2038 Bracket-index the Implementation Link when the name is not an identifier - Merge pull request #2030 from LarryThiessen/fix-2028-restore-version-nodes
#2028 Build the tree after restoring its state, so versions show on open - #2028 Time the tree build where it now happens
The build moved below the expansion/selection restore but its timestamps
stayed where it used to be, so afterTree - afterCreate bracketed nothing:
ReportOpenTiming printed "ManageTree 0" on every open and the build's real
cost went silently into the select+show bucket instead.
That report is not decorative -- it prints to the user unprompted above
2000 ms, and its whole job is to say which of CreateEditor / ManageTree /
select+show owns a slow open. It is also the measurement anyone would use
to answer what this change costs on open.
Bracket the build at each of its two call sites instead. The restore work
in between now belongs to neither bucket and surfaces as the gap between
the three numbers and the total, which is what the comment above
openStartedAt already tells you to read a gap as.
Co-Authored-By: Claude Opus 5 noreply@anthropic.com - #2038 Bracket-index the Implementation Link when the name is not an identifier
Built as =GSE.V.() by concatenation, a variable whose name is not a
bare Lua identifier got a link that does not reference it: for
SLG-SBAssist-LvL the result is valid Lua meaning GSE.V.SLG - SBAssist -
LvL(), so it is not caught as malformed and fails at runtime with an
arithmetic-on-nil that is swallowed. The sequence produces nothing while
the editor's own Current Value box -- which indexes GSE.V[name] -- shows
the correct output.
Emit dot syntax for a valid identifier and bracket syntax otherwise,
matching how GSE already references variables internally (Storage.lua
BooleanVariables keys, the QoL Tab menu insertions, and the editor
preview). Both build sites go through one helper.
Co-Authored-By: Claude Opus 5 noreply@anthropic.com - #2036 Fix the tree assigning a string rather than an ID for Global Sequences in its indexes
- Merge pull request #2034 from LarryThiessen/fix-2033-honest-fit-cache
#2033 Fit cache must match the frame, not just the last computation - Merge pull request #2029 from LarryThiessen/fix-2027-dependency-list-layout
#2027 Clear the dependency headers and tighten the row pitch - Merge pull request #2022 from LarryThiessen/fix-2021-tab-line-anchor
#2021 Anchor the Tab session to the caret's line; recolour after picks - Merge pull request #2026 from LarryThiessen/fix-2024-editbox-pool-reset
#2024 Restore EditBox text colour and enabled state on widget reuse - #2033 Fit cache must match the frame, not just the last computation
The last macro block of a version could draw at baseline height with its
text clipped, and neither typing nor reopening the version fixed it.
FitMacroEditBoxToContent early-returns when a fit computes the same
height it computed last time -- but a later layout pass can shrink the
box's FRAME while gseFitHeight remembers the correct number, after which
every subsequent fit computes that same correct number, matches the
cache, and returns without repairing the frame. Stuck, with no path
back.
The no-op check now also verifies the frame's live height (within 1px)
before returning, so a frame that drifted from the cached value gets
re-applied. The fit is self-healing against any layout pass that resizes
a box behind its back.
Co-Authored-By: Claude Fable 5 noreply@anthropic.com - Merge pull request #2032 from LarryThiessen/fix-2031-counter-ruler
#2031 Live X/255 counter measures the compiled length, like the Save gate - #2031 Live X/255 counter measures the compiled length, like the Save gate
Push a block over 255 and Save greys; delete until the counter reads
under 255 and Save stays greyed. The gate and the red backdrop measure
the COMPILED body length (TranslatorMode.String -- what WoW enforces),
but the live counter in OnTextChanged measured the raw display text.
Translated spell names are usually longer than what is typed, so the
counter dropped under 255 while the compiled body stayed over: the gate
was right and the counter lied. The draw-time counter already used the
compiled length -- the live path had diverged from #1998's stated
intent.
The live counter now uses GetCompiledMacroBodyLength(storedMacro): one
ruler for the counter, the red trigger and the Save gate. It may read
higher than the raw typed text; that is the truthful number.
Co-Authored-By: Claude Fable 5 noreply@anthropic.com - #2028 Build the tree after restoring its state, so versions show on open
Opening the editor onto the restored sequence showed only Configuration
and New Version -- no version nodes. Version children are built lazily
(#2014): versionsWanted reads the tree's expanded/selected state at
BUILD time, but ShowSequences built the tree before restoring that
state, and the OnGroupExpanded rebuild only fires on a click, never on
restore.
The build moves below the state restore, keeping exactly one build per
open. The normal path marks the restored sequence node itself expanded
(segment 3 of the saved path) so its versions are wanted, then builds,
then selects; the detached-tree path builds after mirroring the source
tree's groups, before SetSelected.
Co-Authored-By: Claude Fable 5 noreply@anthropic.com - #2027 Clear the dependency headers and tighten the row pitch
The first data row of the Dependencies list drew on top of the
Name/Type/Author/Date column headers: the data scroll's 8px top padding
does not clear the header band drawn on the box frame (top 4 + height
15). And 22px rows with 20px labels and a 2px list gap around ~12px text
read as double-spaced.
Top padding now clears the band (12 -- the scroll content already starts
inset below the frame top, so less than the band's full 19 is needed,
and the comment ties the numbers together). Row height 22 -> 16, column
label height 20 -> 16, list gap 2 -> 0. Applied to both dependency views
so they stay identical.
Co-Authored-By: Claude Fable 5 noreply@anthropic.com - #2024 Restore EditBox text colour and enabled state on widget reuse
An EditBox released while disabled (or with a caller-set text colour)
kept that state into its next life -- the version-label box came back
GREY after a version delete redrew the pane. SetTextColor and Disable()
live on the frame itself, invisible to all three existing reset sweeps
(table keys, regions/children, scripts), and the FontString styling
restore does not cover EditBox frames.
The per-subframe pristine snapshot now also records GetTextColor and
IsEnabled for text-bearing frames, and reuse restores both.
Co-Authored-By: Claude Fable 5 noreply@anthropic.com - #2021 Anchor the Tab session to the caret's line; recolour after picks
With the caret on a middle line, a picked Spell landed at the end of the
box's LAST line. Probe-proven: the session anchored itself to a byte
offset, and the box is rewritten UNDER the open menu -- the macro
editor's deferred compile (scheduled by the focus-loss that opening the
menu itself triggers) normalises the text, so every byte offset silently
slides onto a different line.
The session now anchors to the caret's LINE INDEX -- newlines survive
both recolouring and compile normalisation -- and every read re-derives
fresh offsets from the live text via lineBounds() (clamped past the end,
so a rewrite that removes lines cannot walk the anchor off the box).
Picks that open a new row advance the anchor; undo snapshots and
restores it. The visible caret parks at the line END at session start
instead of visibly jumping to the front.
The box also never recoloured after building a line: splice()'s
recolour-token bump -- needed to invalidate stale debounced repaints
from EARLIER edits -- also cancelled the repaint its own SetText just
queued, and no second focus-loss ever fires to run the commit repaint
(the box lost focus when the menu opened; nothing refocuses it). Every
content-producing pick now runs the same cosmetic repaint the
live-typing debounce uses.
And because the mid-session compile trims trailing spaces, pickSpell
supplies its own separator (", " after a spell, " " after any other
non-space ending) instead of assuming the line still ends in one --
"/cast [combat]" + pick gave "[combat]Spell" without it.
Co-Authored-By: Claude Fable 5 noreply@anthropic.com - #2020 Fix spell cache and cross language translation issues...
3.3.30
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...
3.3.29
GSE
3.3.29 (2026-08-22)
Full Changelog Previous Releases
- #1963 Check label usage and move Notes to a rendered view
- #1963 Fix terminology in tooltip. Sequences are not macros.
- Merge pull request #2001 from LarryThiessen/fix-1998-macro-box-autofit
#1998 Auto-size the macro-block command box to its content - #1998 Macro box minimum is 3 rows, and the fit counts line spacing
Drop MACRO_BOX_MIN_LINES to 3 so a short block stops reserving five rows.
The minimum was doing double duty as the widget's build height, and that
height is load-bearing: NativeUI's SetNumLines(n) is n * 16 +
STYLE.frameContentTop (28), so five rows IS macrolayout's SetHeight(108),
and the fit pushes the DELTA from that baseline up through the
fixed-height ancestors. Lowering the shared constant would have built the
box at 76 against a 108 container and left ~32px of dead space under every
block, so the build height is now its own MACRO_BOX_BASE_LINES = 5 with
the arithmetic written down at both sites.
Two font-size gaps the smaller minimum exposes:- oneRow is measured from a single "X" and so carries no line spacing, but
the row clamps multiplied it straight out. Real N-row text is
N * oneRow + (N - 1) * spacing, so with a nonzero GetSpacing() the min
clamp fell short of a true N rows and clipped the bottom one. - NativeUI clamps the inner editbox to 40px (updateEditBoxSize). Five
rows always cleared it; three rows at a small chat font do not, and the
last row would render outside the visible scroll area. The frame is
never asked for less than that floor now.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com
Claude-Session: https://claude.ai/code/session\_01JaKGoo9zcWMDowDLy4cAbX
- oneRow is measured from a single "X" and so carries no line spacing, but
- Merge pull request #2000 from LarryThiessen/fix-1997-tab-insert-at-caret
#1997 Tab spell list: insert at the remembered caret, not the end of the row - Merge pull request #1999 from LarryThiessen/fix-1996-caret-sync-restore
#1996 Restore the macro-block caret synchronously after a live repaint - #1998 Auto-size the macro-block command box to its content
The "Macro Name or Macro Commands" box was a fixed 5 rows, so a 7-8 line
block always scrolled. FitMacroEditBoxToContent measures the rendered
text height with a hidden FontString in the box's own font (wraps
included; estimating rows x font size drifted by a row), clamps to 5..24
rows, ignores a stored trailing newline except while the author is
typing on that last row, and dedupes on the resulting height. It runs
once after the block body is assembled and on every text change.
Because the box sits under explicit-height containers (macrolayout,
macroFields, macroBody, sized for the old 108px box), the fit pushes the
height delta up through every fixed-height ancestor before auto-height
ones re-lay out -- otherwise the box grew but overlapped the Repeat
Interval row below it.
Fixes #1998
Co-Authored-By: Claude Fable 5 noreply@anthropic.com - #1997 Tab spell list: insert at the remembered caret, not the end of the row
Between pressing Tab and clicking the entry, the editor's debounced live
repaint (or a focus-loss commit repaint) can SetText the box, parking the
caret at the end -- so a plain Insert sometimes landed at the end of the
last row. Remember the caret at Tab-press, cancel any pending live repaint
via the gseRecolourToken the recolour already honours, and on pick do
SetFocus + SetCursorPosition(saved) + Insert.
Fixes #1997
Co-Authored-By: Claude Fable 5 noreply@anthropic.com - #1996 Restore the macro-block caret synchronously after a live repaint
RefreshMacroEditorColoredText repaints the coloured text with SetText,
which parks the caret at the end, and only restored it via a deferred
C_Timer.After(0) -- so a frame rendered with the caret (and the scroll
view chasing it) at the end of the bottom line before it moved back: a
visible flick on every live repaint, and a fast follow-up key acted at
the end of the text. An unconditional deferred restore could also yank
the caret back to a stale position one frame after a newer keystroke.
Restore the caret synchronously right after SetText (the same
SetText+SetCursorPosition-in-one-go approach IndentationLib has used for
years). Keep the deferred restore only as a backstop: scheduled only if
the immediate restore visibly did not take, and it re-checks that the
text is still the text it was computed for before touching the caret.
Colouring logic is untouched.
Fixes #1996
Co-Authored-By: Claude Fable 5 noreply@anthropic.com - Merge pull request #1995 from LarryThiessen/fix-1994-tab-menu-stale-owner
#1994 Anchor the Tab spell-list menu to the live editbox, not a stale editor frame - #1994 Anchor the Tab spell-list menu to the live editbox, not a stale editor frame
After a /reload the #1989 Tab spell list worked, then silently stopped
appearing once the editor had been re-created (e.g. across a combat
transition). The handler still fired; the menu never opened. The owner
it anchored to reported hidden: GSE.CreateSpellEditBox is defined once,
guarded by GSE.isEmpty, inside the first editor ever created, so the
editframeinside it is a permanent upvalue to that first editor. The
#1989 wiring passed editframe.frame as the menu owner instead of the
factory'sframeparameter (the current editor's frame), and Blizzard
will not open a context menu on a hidden owner.- Editor.lua: pass the factory's
frameparameter to the Tab hooks. - QoL.lua: anchor all four Tab menus to the editbox itself -- visible by
definition when Tab fires -- the same approach the icon Select menu
already takes with its panel-local frame.
Fixes #1994
Co-Authored-By: Claude Fable 5 noreply@anthropic.com
- Editor.lua: pass the factory's
3.3.28
GSE
3.3.28 (2026-08-21)
Full Changelog Previous Releases
- #1993 remove unrelated files
config.ld, publishGSETools.js and uploadLocale.js were being packaged into
the GSE folder. None of them are addon code: config.ld configures ldoc,
publishGSETools.js posts the release to gse.tools, and uploadLocale.js is the
new pre-build localisation upload. Build tooling, not something to ship to
players.
Listed twice, as the other build scripts already are — once at the root and
once GSE/-prefixed — because move-folders maps GSE/GSE onto GSE, so the
packager sees both paths.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com
Claude-Session: https://claude.ai/code/session\_017wmCKbEwmEgWW8SpWnXJhC - #1989 Restore the Tab spell list in the Sequence Editor
#1989 Restore the Patron Tab spell list in the Sequence Editor - Add spec/run51.lua — run the suite on the interpreter CI actually uses
Local busted is installed for 5.4 only; CI is PUC Lua 5.1.5
(leafo/gh-actions-lua@v10 + luarocks busted). So a spec can use anything 5.2
added, pass every local check, and fail in CI. That has now happened twice on
the same construct — load(chunk, name, mode, env), where 5.1's load takes a
reader FUNCTION and a string chunk needs loadstring + setfenv.
lua5.1 spec/run51.lua # matches CI, exits 1 on failure
busted # full luassert, 5.4
Run both. Verified by reintroducing the exact bug: run51 fails with CI's
error verbatim while busted stays green.
Deliberately NOT a busted replacement — it implements the globals the GSE
specs use (describe/it/setup/before_each and the luassert matchers), so a
spec reaching for more will fail here and needs busted too.
Two details that matter:
- one spec per PROCESS. Specs leak globals — CreateFrame is defined only by
keydownbinding_spec — so a shared interpreter makes results depend on
file order.
- checksum_spec reports SKIPPED, not failed: it needs luabitop'sbit
global, which cannot be installed here (no luarocks binary). CI and
busted both still cover it, and a runner that always shows a failure is
a runner nobody reads.
Do NOT substitute luajit for lua5.1. LuaJIT extends 5.1 and accepts
load(string, ...), so it passes the very bug this exists to catch.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com
Claude-Session: https://claude.ai/code/session\_017wmCKbEwmEgWW8SpWnXJhC - #1989 Load the spec's chunk the way Lua 5.1 wants
CI runs busted under Lua 5.1, whereloadtakes a reader FUNCTION — a string
chunk goes through loadstring, and its environment is attached with setfenv.
5.2+ removed both and added load(chunk, name, mode, env), which is what this
used, so all six tests errored with "bad argument #1 to 'load' (function
expected, got string)" while passing locally on 5.4.
Both forms are supported now, chosen on whether setfenv exists rather than on
which interpreter happens to be installed. Verified by running the same
extraction under lua5.1 and lua5.4 directly: identical spell lists from both.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com
Claude-Session: https://claude.ai/code/session\_017wmCKbEwmEgWW8SpWnXJhC - #1989 Enumerate the spellbook on Classic too, not just Retail
getPlayerSpells returned an empty table when C_SpellBook was missing, so on
Classic the Tab menu opened with an Insert Spell heading and nothing beneath
it. Worse, the guard tested the TABLE: TBC Classic Anniversary and MoP
Classic expose C_SpellBook without all of its functions, so they passed the
check and then found no skill-line API — the same trap documented on
spellIDIsInSpellBook in GSE/API/translator.lua, which is why the choice is
now made per FUNCTION, requiring every call the modern loop makes.
The Classic path walks tabs rather than skill lines: GetNumSpellTabs /
GetSpellTabInfo for the offsets, GetSpellBookItemName for the name, and
IsPassiveSpell plus GetSpellBookItemInfo for the filtering the modern API
does with fields on an info table. Three Classic-specific behaviours the
Retail loop has no equivalent for:
- ranks. Classic lists one row per rank, so names are deduped. A macro
wants the name anyway — /cast already picks the highest rank
known.
- FUTURESPELL rows, which are the greyed-out not-yet-learned entries, are
excluded; only "SPELL" is castable.
- offSpecID from GetSpellTabInfo, Cata+ only and nil before it, stands in
for isOffSpec so another spec's book is skipped.
A client exposing neither API returns an empty list rather than erroring,
which is what happened before on every Classic client.
.luacheckrc gains the five Classic globals; without them the build fails on
undefined-variable warnings.
spec/spellbook_spec.lua covers both APIs, the partial-C_SpellBook client, the
off-spec tab, a client with no item-info call, and no spellbook API at all —
none of which can be reached from a Retail test run.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com
Claude-Session: https://claude.ai/code/session\_017wmCKbEwmEgWW8SpWnXJhC - #1991 Add toggle to turn on manual reset message
A configured manual reset announced itself in chat every single time it
fired. The user pressed the reset themselves, so for anyone who resets often
that line is just noise. It is now off by default and opt-in via
GSEOptions.AnnounceMacroReset, with a checkbox on Tools & Diagnostics.
The message lives in Statics.MacroResetSkeleton — secure-environment code
stamped onto the button when it is built — so it cannot read GSEOptions at
the moment it runs. The skeleton is therefore two forms, silent and
announcing, chosen in GSE.GetMacroResetImplementation while still in normal
Lua. That also means a live toggle does not take effect until the button is
rebuilt, so the setting prompts for a reload exactly as the modifier-pause
toggles on the same page already do.
The reset itself is unchanged with the announcement off — SetAttribute('step',- still runs, which is the part worth pinning, and spec/macroreset_spec.lua
does: silent by default, still resets, announces when enabled, a nil option
reads as off rather than erroring, and no configured modifier still emits no
snippet at all.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com
Claude-Session: https://claude.ai/code/session\_017wmCKbEwmEgWW8SpWnXJhC
- still runs, which is the part worth pinning, and spec/macroreset_spec.lua
- #1993 Have build process upload localisations.
The documented request shape does not work. CurseForge's docs describe "a
POST request with a JSON data blob"; posting that returns HTTP 500
"unhandled exception" (observed on the 2a5d73c build). p3lim's
curseforge-localizations, which works, sends multipart/form-data with two
named parts — metadata as a JSON string, localizations as the blob — to
legacy.curseforge.com rather than the wow.curseforge.com in the docs, and
omits formatType entirely since TableAdditions is the default.
Verified against a local echo server rather than the documentation this
time: the request now arrives as multipart with an X-Api-Token header and
metadata/localizations parts, matching the working client's shape. CF_API_BASE
exists to point the request at that echo server.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com
Claude-Session: https://claude.ai/code/session\_017wmCKbEwmEgWW8SpWnXJhC - #1993 Have build process upload localisations.
CurseForge holds the phrase list translators work against, and only learns
about a new or changed string when someone uploads it — so a release could
ship strings nobody had ever been offered a translation for. npm run
upload-locale now runs immediately before the packager, posting enUS to
/api/projects/{id}/localization/import with the CF_API_KEY the packager
already uses. Project id comes from X-Curse-Project-ID in the TOC rather than
being hardcoded.
missing-phrase-handling is DeletePhrase: the addon's enUS file is the master
list, so a phrase no longer in it should not sit on CurseForge collecting
translations for a string nothing displays. That makes the upload
DESTRUCTIVE, hence two guards — the run refuses on an empty parse, and
refuses when the parsed count falls below half theL[statements a crude
independent scan finds. A parser regression then fails the step instead of
telling CurseForge the addon has three phrases and to delete the rest.
CF_LOCALE_MISSING_HANDLING=DoNothing overrides per run; --dry-run prints the
payload without posting.
The file is parsed, not regexed. AceLocale'sL["x"] = truemeans the value
IS the key, which TableAdditions cannot express, so it expands on the way
out; and 36 entries wrap across lines, one key carries an escaped quote, and
several values are multi-line. Validated by having Lua itself read the same
file and diffing every key and value, which caught two real bugs: \ddd
escapes are BYTES (an em dash is ...
3.3.27
3.3.26
GSE
3.3.26 (2026-08-09)
Full Changelog Previous Releases
- #1976 Localisation update
- #1976 Update Help information
- #1976 Classic TOC updates
- #1965 Deltas broke exporting of variables
- Merge pull request #1975 from TimothyLuke/dependabot/npm_and_yarn/brace-expansion-5.0.9
Bump brace-expansion from 5.0.8 to 5.0.9 - #1971 Fix ElvUI and Actionbar overrides when farming in MoP
- Bump brace-expansion from 5.0.8 to 5.0.9
Bumps brace-expansion from 5.0.8 to 5.0.9.
updated-dependencies:- dependency-name: brace-expansion
dependency-version: 5.0.9
dependency-type: indirect
...
Signed-off-by: dependabot[bot] support@github.com
- dependency-name: brace-expansion
- Merge pull request #1973 from TimothyLuke/dependabot/npm_and_yarn/undici-6.28.0
Bump undici from 6.24.1 to 6.28.0 - update Build process post library update.
- Bump undici from 6.24.1 to 6.28.0
Bumps undici from 6.24.1 to 6.28.0.
updated-dependencies:- dependency-name: undici
dependency-version: 6.28.0
dependency-type: indirect
...
Signed-off-by: dependabot[bot] support@github.com
- dependency-name: undici
- Merge pull request #1972 from TimothyLuke/dependabot/npm_and_yarn/multi-98c7da4b5b
Bump brace-expansion and archiver - Bump brace-expansion and archiver
Bumps brace-expansion to 5.0.8 and updates ancestor dependency archiver. These dependencies need to be updated together.
Updatesbrace-expansionfrom 1.1.13 to 5.0.8- Release notes
- Commits
Updatesarchiverfrom 5.3.1 to 8.0.0 - Release notes
- Changelog
- Commits
updated-dependencies:- dependency-name: brace-expansion
dependency-version: 5.0.8
dependency-type: indirect - dependency-name: archiver
dependency-version: 8.0.0
dependency-type: direct:production
...
Signed-off-by: dependabot[bot] support@github.com
3.3.25
GSE
3.3.25 (2026-07-17)
3.3.24
GSE
3.3.24 (2026-07-14)
Full Changelog Previous Releases
- #1965 TOC maintenance
- Merge pull request #1968 from LarryThiessen/fix-1967-corrupt-seq-tree-blank
FIX: don't blank the editor tree on a corrupt sequence; flag it instead - FIX: don't blank the editor tree on a corrupt sequence; flag it instead
Fixes #1967- ManageTree wraps each sequence's node build in a pcall so one corrupt record
can't blank the whole tree (was: ipairs(nil) on a record missing Versions). - Structurally-broken Library seqs are flagged (red + alert icon) with a
Delete-only right-click menu; clicking one surfaces a message instead of
decoding the broken data and crashing. - Decode-broken seqs (GSE.CorruptSequences) also render in the tree so a
dismissed corrupt-sequence popup still leaves them findable/deletable;
ProcessCorruptSequences no longer drains that list, and both delete paths
prune it (new GSE.ForgetCorruptSequence). - Only flag records that are actually in the Library and broken, so a
comma-in-name seq (comma-joined tree key) isn't false-flagged.
Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com
- ManageTree wraps each sequence's node build in a pcall so one corrupt record
- #1965 Forks for Variables and Macros
- #1965 CBOR unittest
- #1965 Fork imports
- #1965 More Fork Deltas
- #1965 fork support
- #1965 Initial DIFF tool
- #1893 Add function to report issues viua the companion
3.3.23
GSE
3.3.23 (2026-06-20)
Full Changelog Previous Releases
- Merge pull request #1964 from LarryThiessen/fix-abo-icon-not-clearing
FIX: ABO icon not clearing until mouseover (#1963) - FIX: clear ABO icon immediately on removal (no mouseover needed)
#1963
Removing an Action Button Override cleared the GSE watermark but left the
GSE-painted icon on the button until the next ActionButton update (e.g. a
mouseover). LoadOverrides reverted the button and removed the watermark but
never repainted the real action-slot icon.
LoadOverrides now snapshots the overridden buttons and, after the re-arm pass,
forces a real-icon repaint on any button that was cleared and not re-armed
(button:Update() / ActionButton_Update / direct slot-texture fallback; all
guarded for cross-version safety).
Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com - #1975 Normalise Skin variances
- #1957 move Skyriding to part of tools and diagnostics. its only relevant to people who use keybinds (< 20% of GSE Users)
- Merge pull request #1959 from LarryThiessen/enhancements-1957-editor
ENH: Editor toolbar/layout, live tree updates, options ordering - #1893 Handle Macros via import strings
- #1893 Import cleanup
- Fix #1960 Frames that dont render correctly
- Merge pull request #1958 from LarryThiessen/fixes-1956-editor-options
FIX: MacroBlock editor editing bugs + blank Options History panel - ENH: Editor toolbar/layout + live tree updates + options ordering
#1957- Editor: swap Pause/If toolbar order; vertically center the Version Name box;
top-button block creation inserts a sibling immediately after the focused
block (focusing a Loop/If adds below it, not inside). - Tree: new versions appear instantly and deleted versions clear instantly,
before saving (mirrored into the in-memory Library cache). - Options: move Skyriding / Vehicle Keybinds above Tools & Diagnostics.
Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com
- Editor: swap Pause/If toolbar order; vertically center the Version Name box;
- FIX: MacroBlock editor live-colour editing bugs + blank Options History panel
#1956- Editor: live syntax-colouring no longer eats the blank row opened with Enter,
stops backspace at a comma, or throws the caret to the end. The live re-colour
is now idempotent (repaints only when no visible characters change) and
debounced, so it can't rewrite in-progress text or fight the caret. - Options: the GSE History/About page no longer comes up blank - its content is
built eagerly instead of only in OnShow (a canvas parent with subcategories
does not reliably fire OnShow).
Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com
- Editor: live syntax-colouring no longer eats the blank row opened with Enter,
- #1914 TOC Updates