File Details
v0.4.8
- R
- Jun 29, 2026
- 582.91 KB
- 2.6K
- 12.0.7+3
- Classic + 3
File Name
BetterBags-v0.4.8.zip
Supported Versions
- 12.0.7
- 5.5.4
- 2.5.5
- 1.15.8
BetterBags
v0.4.8 (2026-06-29)
Full Changelog Previous Releases
- fix(bank): replace non-existent PLAYER_BANK_TAB_SLOTS_CHANGED event with BANK_TABS_CHANGED (#976)
- Fix Bank Bag Slot Pane updates, Retail character bank tab registration, Warbank configuration right-click, and closed-bank filter leak
- Fix a Classic/Era typo in frames/bagslots.lua: register PLAYERBANKSLOTS_CHANGED instead of PLAYERBANKBAGSLOTS_CHANGED to ensure slot updates trigger redraws when purchasing bank slots on Classic/Era.
- Fix Retail character bank tab registration: register PLAYER_BANK_TAB_SLOTS_CHANGED in both frames/bankslots.lua and bags/bank.lua so character bank tab purchases cleanly regenerate group tabs and redraw.
- Fix Warbank right-click configuration trigger in frames/bankslots.lua: dynamically resolve BankFrame.BankPanel and BankFrame.AccountBankPanel with fallbacks, resolving the silent menu open failure under unified Retail bank layouts.
- Fix filter leak on closed bank: reset blizzardBankTab filter, selected slot indices, and panel styling during bank frame's OnHide, preventing permanently filtered bank displays.
- Guard tabsWereShown in frames/bagslots.lua and frames/bankslots.lua to prevent overwriting of the flag to false during redundant panel Show calls.
- Add fully verified unit tests in spec/frames/bankslots_spec.lua and expand wow_mocks.lua to guarantee reliability.
- fix(bank): resolve Classic/Era OnClose nil method error on close and fix luacheck linter warnings
- Fixes a Classic/Era regression where closing the bank frame throws 'attempt to call method OnClose (a nil value)'. This is resolved by adding a robust 'OnClose' method directly to the 'BagSlots.bagSlotProto' in 'frames/bagslots.lua', as well as guarding 'self.bag.slots:OnClose(ctx)' inside 'bags/bank.lua' so that it safely checks for the method's existence before calling.
- Cleans up and restores the 'tabsWereShown' group tabs visibility on Classic/Era when the slots panel is hidden.
- Resolves two Luacheck shadowing warnings in 'spec/helpers/wow_mocks.lua' by renaming the 'name' parameter in 'SetAttribute' and 'GetAttribute' inside 'CreateMockWidget' to 'attrName', avoiding shadowing the upvalue argument 'name'.
- Resolves Luacheck unused argument warning in 'frames/bagslots.lua' by renaming 'ctx' to '_ctx' on 'OnClose'.
- Adds a dedicated TDD unit test case reproducing the Classic/Era regression in 'spec/frames/bankslots_spec.lua', mocks 'GetNumBankSlots' for Classic/Era specs, and cleans up trailing whitespace lint warnings.
- fix(bank): replace non-existent PLAYER_BANK_TAB_SLOTS_CHANGED event with BANK_TABS_CHANGED
The event 'PLAYER_BANK_TAB_SLOTS_CHANGED' does not exist in World of Warcraft's official event API registry, causing an immediate 'Attempt to register unknown event' crash on boot.
According to Blizzard's official API documentation in BankDocumentation.lua:
- 'PLAYER_ACCOUNT_BANK_TAB_SLOTS_CHANGED' is used specifically for account/warbank tab slots.
- 'BANK_TABS_CHANGED' is used for both character and warbank tab purchases/modifications.
This replaces 'PLAYER_BANK_TAB_SLOTS_CHANGED' with 'BANK_TABS_CHANGED' in both 'bags/bank.lua' and 'frames/bankslots.lua' to correctly and safely handle character bank tab changes, and updates the unit tests to match.
- Fix Bank Bag Slot Pane Bugs, Retail Character Bank Tabs, and Warbank Configuration (#975)
- Fix Bank Bag Slot Pane updates, Retail character bank tab registration, Warbank configuration right-click, and closed-bank filter leak
- Fix a Classic/Era typo in frames/bagslots.lua: register PLAYERBANKSLOTS_CHANGED instead of PLAYERBANKBAGSLOTS_CHANGED to ensure slot updates trigger redraws when purchasing bank slots on Classic/Era.
- Fix Retail character bank tab registration: register PLAYER_BANK_TAB_SLOTS_CHANGED in both frames/bankslots.lua and bags/bank.lua so character bank tab purchases cleanly regenerate group tabs and redraw.
- Fix Warbank right-click configuration trigger in frames/bankslots.lua: dynamically resolve BankFrame.BankPanel and BankFrame.AccountBankPanel with fallbacks, resolving the silent menu open failure under unified Retail bank layouts.
- Fix filter leak on closed bank: reset blizzardBankTab filter, selected slot indices, and panel styling during bank frame's OnHide, preventing permanently filtered bank displays.
- Guard tabsWereShown in frames/bagslots.lua and frames/bankslots.lua to prevent overwriting of the flag to false during redundant panel Show calls.
- Add fully verified unit tests in spec/frames/bankslots_spec.lua and expand wow_mocks.lua to guarantee reliability.
- fix(bank): resolve Classic/Era OnClose nil method error on close and fix luacheck linter warnings
- Fixes a Classic/Era regression where closing the bank frame throws 'attempt to call method OnClose (a nil value)'. This is resolved by adding a robust 'OnClose' method directly to the 'BagSlots.bagSlotProto' in 'frames/bagslots.lua', as well as guarding 'self.bag.slots:OnClose(ctx)' inside 'bags/bank.lua' so that it safely checks for the method's existence before calling.
- Cleans up and restores the 'tabsWereShown' group tabs visibility on Classic/Era when the slots panel is hidden.
- Resolves two Luacheck shadowing warnings in 'spec/helpers/wow_mocks.lua' by renaming the 'name' parameter in 'SetAttribute' and 'GetAttribute' inside 'CreateMockWidget' to 'attrName', avoiding shadowing the upvalue argument 'name'.
- Resolves Luacheck unused argument warning in 'frames/bagslots.lua' by renaming 'ctx' to '_ctx' on 'OnClose'.
- Adds a dedicated TDD unit test case reproducing the Classic/Era regression in 'spec/frames/bankslots_spec.lua', mocks 'GetNumBankSlots' for Classic/Era specs, and cleans up trailing whitespace lint warnings.
- feat: unify bag slots positioning with uniform bottom-bar layout and group-tabs auto-hiding (#974)
- Registered backpack slots with empty title headers under all clients so they render as borderless flat windows, perfectly matching bank slots.
- Positioned shown backpack slots to dock directly to the bottom of the parent frame, occupying the space where group tabs normally sit.
- Added automatic hiding of group tabs when the backpack slots panel is active, and restored their visibility when closed.
- Updated the theme apply positioning fallback and GW2 custom positioner in themes/themes.lua and themes/gw2.lua to uniformly bottom-align all shown slots panels.
- Added comprehensive unit tests in spec/themes_spec.lua to validate the new layout and verified linter/test compliance.
- fix(theme): fix retail bank slots positioning under GW2 theme (#973)
- Updated PositionBagSlots in themes/gw2.lua to be retail bank-aware.
- Anchors the retail bank slots panel to the bottom when shown, and to the top-left start/slide point when hidden, matching the default and other themes.
- Keeps standard GW2 top-slide-out positioning (8, 16) for retail backpack slots, and all panels under Classic/Era.
- Added comprehensive unit tests in spec/themes_spec.lua to validate correct positioning for shown/hidden retail bank slots, backpack slots, and classic bank slots.
- Verified clean luacheck and all 838 unit tests passing.
- fix: resolve on-the-fly bank slots anchoring and sizing during theme switch (#972)
- Introduce dynamic anchoring of bank slots during live theme change: if the slots panel is shown, anchor it to the bottom of the bank frame; if hidden or if it's the backpack, anchor it to the top starting position.
- Respect theme custom PositionBagSlots overrides.
- Trigger real-time slots panel redraw (Draw) for currently shown slot panels when changing themes to adapt sizes and offsets instantly.
- Add comprehensive unit tests in spec/themes_spec.lua to validate dynamic positioning and redraw behavior.
- Fix bank/bag slots flat window header overlap in Default theme (#971)
Introduce a theme-aware API `themes:GetFlatHeaderHeight(frame)` to dynamically query the height offset needed for flat windows depending on the active theme's style and title status:- If a frame has a non-empty title, it always returns 30px to accommodate the title bar.
- If the title is empty:
- In the Default theme (relying on DefaultPanelFlatTemplate), it returns 30px because a visual header bar/top border is rendered.
- In headerless/borderless themes like Simple Dark or GW2, it returns 0px (or the theme's custom callback value if provided).
Apply this theme-aware calculation in bank/bag slots drawing:
- Dynamically calculate the topInset in `frames/bankslots.lua` and `frames/bagslots.lua` (both retail and era/classic).
- Re-anchor the content container and adjust the overall frame height dynamically on Draw to prevent visual overlaps of flat frame templates with slot buttons.
- Document the new API in `themes/README.md`.
- Add comprehensive unit tests in `spec/themes_spec.lua` and mock the standard WoW GetName API in `spec/helpers/wow_mocks.lua` to thoroughly validate correct layout behavior across multiple themes under TDD.
- style: change custom category item list font to normal (#970)
Changed the font outline style from 'THICKOUTLINE' to standard/normal font by setting the flag to an empty string in both retail and classic/era 'itemrow.lua' files. This removes the overly thick black text border/stroke and makes the list items look clean and consistent with the rest of the options UI.
Also updated the unit tests in 'spec/itemrow_spec.lua' to verify that empty/normal font flags are successfully validated and initialized. - fix: resolve top show-stopping bugs and enforce Test-First TDD (#969)
- Establish PRIME DIRECTIVE #1 in CLAUDE.md: All future changes must follow a strict Test-First (TDD) approach.
- Fix Categories panel blank item list (Issue #946): Update SetFont call in frames/itemrow.lua and frames/era/itemrow.lua to use valid 'THICKOUTLINE' flag instead of 'THICK'. Update wow_mocks.lua SetFont mock to validate flags. Add unit tests in spec/itemrow_spec.lua.
- Fix Retail Battle Pet tooltip crash (Issue #939): Before calling C_TooltipInfo.GetBagItem, check the container item link. If it's a battle pet, bypass the background scan. Add unit test in spec/tooltip_spec.lua.
- Fix dynamic item level stuck on consumables (Issue #958): Guard database:UpdateMaxItemLevel in data/items.lua to only update on Weapon and Armor classes. Implement database:ResetMaxItemLevel() in core/database.lua and add a 'Reset Max Item Level' button in config/itemcolorpane.lua. Add unit tests in spec/items_spec.lua.
- Fix cross-test and missing mock issues: Guard C_Bank.IsItemAllowedInBankType in data/binding.lua, mock missing Frame/Texture APIs (EnableMouseWheel, SetTextHeight, SetWordWrap, SetShadowColor, SetColorTexture, SetDrawLayer, SetShown), mock Enum.ItemClass, and properly manage BINDING_SCOPE and resetting stubs in specs to eliminate cross-test pollution.
- Fix four bugs in data stack (refresh, items, stacks, and categories) (#968)
- Expand WoW mocks and implement comprehensive unit tests for Money, Section, and BagView frames
- Enhanced spec/helpers/wow_mocks.lua with a unified stateful widget factory, C_Bank, C_Container, and GameTooltip mocks.
- Implemented standard Item, ItemLocation, and ContinuableContainer mixins to support presentation layer requirements.
- Added comprehensive unit tests in spec/frames/money_spec.lua to verify copper/silver/gold split, static popup interaction, and PLAYER_MONEY event loops.
- Added spec/frames/section_spec.lua to verify categories grids, titles formatting, collapsed states, and pooling behaviors.
- Added spec/views/bagview_spec.lua to verify blank views, wipe cleanup, and bag view render pathways.
- All 819 unit tests pass successfully, and luacheck linting reports zero warnings or errors.
- Fix four bugs in data stack (refresh, items, stacks, and categories)
- Bug 1: The Combat-Lockdown Wipe Drop (Refresh Subsystem)
- Modified data/refresh.lua:ExecutePendingUpdates() to check InCombatLockdown() first and return early without clearing pending flags. This ensures flags are retained during combat and processed after PLAYER_REGEN_ENABLED.
- Modified spec/refresh_spec.lua to assert flags are retained and subsequently processed.
- Bug 2: Search Index Leak on ItemChanged (Search Subsystem)
- Modified data/items.lua:LoadItems() to call search:Remove(previousItem) instead of search:Remove(currentItem), preventing search index attribute leaks when item count/properties change.
- Added LoadItems test case in spec/items_spec.lua to verify proper search index removal.
- Bug 3: Redundant Stack Lookups on Disposal (Stacks Subsystem)
- Modified data/stacks.lua:RemoveFromStack() to return early immediately after a stack is cleared (disposed), avoiding useless items:GetItemDataFromSlotKey lookups.
- Added test case in spec/stacks_spec.lua to assert GetItemDataFromSlotKey is called exactly 0 times upon stack disposal.
- Bug 4: Dynamic Plugin Categories Stuck on nil (Categories Subsystem)
- Modified data/categories.lua to clear self.itemsWithNoCategory[id] = nil inside AddItemToCategory, AddPermanentItemToCategory, and RemoveItemFromCategory, preventing stale uncategorized cache hits.
- Added GetCustomCategory test in spec/categories_spec.lua to verify cache clearing on addition and removal.
- Expand WoW mocks and implement comprehensive unit tests for Money, Section, and BagView frames (#967)
- Enhanced spec/helpers/wow_mocks.lua with a unified stateful widget factory, C_Bank, C_Container, and GameTooltip mocks.
- Implemented standard Item, ItemLocation, and ContinuableContainer mixins to support presentation layer requirements.
- Added comprehensive unit tests in spec/frames/money_spec.lua to verify copper/silver/gold split, static popup interaction, and PLAYER_MONEY event loops.
- Added spec/frames/section_spec.lua to verify categories grids, titles formatting, collapsed states, and pooling behaviors.
- Added spec/views/bagview_spec.lua to verify blank views, wipe cleanup, and bag view render pathways.
- All 819 unit tests pass successfully, and luacheck linting reports zero warnings or errors.
- docs: add strict WoW API mocking and testing rules (#966)
Introduce new developer guidelines to CLAUDE.md (and symmetrically AGENTS.md via symlink) that govern WoW API mocking and implementation-level mock usage:- Strict requirement that any new code changes must be validated and tested using mocks.
- Missing WoW API mocks must be derived from Blizzard's official WoW UI source code instead of being guessed or invented.
- Code implementation must adhere and fit to verified mock contracts. Under no circumstances should mocks be manipulated or altered to adapt to implementation shortcuts or bugs.
- metadata: Update Interface version TOC numbers across all client flavors (#965)
Update the WoW interface TOC numbers to the latest release versions to prevent the client from marking the addon as out-of-date:- BetterBags.toc (Retail / Midnight): Updated from 120000, 120001 to 120007 (Patch 12.0.7).
- BetterBags_Vanilla.toc (Classic Era / Vanilla): Updated from 11507 to 11508 (Patch 1.15.8).
- BetterBags_Mists.toc (Mists of Pandaria Classic): Updated from 50501 to 50504 (Patch 5.5.4).
- Enforce Lua 5.1 as the only supported runtime (repo-wide) (#964)
The project's code, libraries, and tests have always targeted Lua 5.1
(WoW's runtime), but the suite had no mechanical guarantee of that: any
interpreter could run `busted`, and a green run on, say, Lua 5.4 would
not be validated by anything in the repo. Make 5.1 the single source of
truth, enforced in three redundant places.
What changed:- spec/setup.lua:1-12 -- new Step 0 guard. Errors out before any library
load or test code if `_VERSION ~= "Lua 5.1"`. Names the detected
version and points at the docs. Cannot be bypassed without editing
the helper, which the .busted config loads for every spec run. - CLAUDE.md -- new "## Lua Version" section between "## Luacheck
Requirement" and "## Deps Requirements". States the 5.1-only policy
in absolute terms (no 5.4 hedging), lists the three enforcement
points with line-number citations, and calls out the practical
consequences: no 5.2+ language features, no "works on 5.4" debugging
shortcuts, install 5.1 yourself (install-deps.sh does not provide an
interpreter). AGENTS.md is a symlink to CLAUDE.md (babc388), so the
change is picked up there automatically. - spec/README.md:4-6 -- front-matter callout pointing at the guard
error and at CLAUDE.md's "Lua Version" section, so a developer who
hits the error lands on the right remediation.
Enforcement already in place, now made explicit in docs: - .github/workflows/test.yml:32 -- CI installs Lua 5.1 via
leafo/gh-actions-lua and pins busted/luacov against it. - .github/workflows/luacheck.yml:19 -- luacheck CI pins Lua 5.1 the
same way. - .luacheckrc:1 -- `std = "lua51"`, so 5.2+/5.3+/5.4-only constructs
(goto, <close>, //, etc.) are lint errors.
Verified on this box: built Lua 5.1.5 from source into
~/.local/lua51, ran `busted` against it, 798/798 pass and the new
guard accepts the run (`_VERSION == "Lua 5.1"`). Re-ran `busted`
against the system Lua 5.4 to confirm the guard fires:
busted: failed running the specified helper (spec/setup.lua), error:
BetterBags test suite requires Lua 5.1 (WoW's runtime). Detected
Lua 5.4. Install Lua 5.1 and `luarocks install busted` against it;
see spec/README.md and CLAUDE.md ("Lua Version").
luacheck . -- 0 warnings / 0 errors in 139 files.
- spec/setup.lua:1-12 -- new Step 0 guard. Errors out before any library
- fix: GroupBucketEvent silently dropped args on Lua 5.1 (WoW) (#963)
Lua 5.1's `xpcall` does NOT pass extra arguments to the function, unlike
Lua 5.4. So:
xpcall(cb, geterrorhandler(), self._eventArguments[joinedEvents])
would call `cb()` with no args in WoW (Lua 5.1) but `cb(table)` on
local Lua 5.4. Every user of BetterBags on the actual WoW client was
running the GroupBucketEvent path with an empty arg list.
Fix: wrap the call in an anonymous function so the table is captured
by closure, matching the pattern BucketEvent already uses on line 125.
Also re-adds the three GroupBucketEvent tests that were removed in
#962 because they only failed on Lua 5.1 (i.e. exactly the bug this
fixes). With the fix, all 3 pass on both 5.1 and 5.4.
Test count: 795 -> 798 on Lua 5.1.
events.lua coverage: 45.8% -> 54.7%. - test: fill coverage gaps across events, async, tooltip, groups, refresh, binding, localization (#962)
Expands test coverage on the under-tested source files and adds a new
spec for the previously-untested Localization module.- New spec/localization_spec.lua (11 tests): G() returns key for missing
translations, falls back when locale is absent, handles multiple keys. - spec/events_spec.lua (+14 tests): RegisterEvent, BucketEvent
(debouncing + cancel-on-refire), GroupBucketEvent (multi-source
bucketing + reset), SendMessageLater. Documents the BucketEvent
replacement-on-reregister behavior. - spec/async_spec.lua (+10 tests): DoWithDelay (delay propagation),
BatchNoYield (no-yield iteration), RawBatch (per-batch yield from
caller coroutine), StableIterate (framerate-based batch size). - spec/tooltip_spec.lua (+16 tests): ExtractClassic with mock
FontStrings, the 30-line cap workaround, OnInitialize retail vs
Classic branch, empty-text extraction handling. - spec/groups_spec.lua (+13 tests): GetGroup, GetAllGroups,
GetGroupForCategory, GetCategoriesInGroup, GetDefaultBankGroup
(character vs warbank), OnCategoryDeleted cleanup across all kinds. - spec/refresh_spec.lua (+9 tests): OnEnable handlers for BAG_UPDATE_DELAYED,
BAG_CONTAINER_UPDATE, EQUIPMENT_SETS_CHANGED, PLAYER_REGEN_ENABLED,
bags/RefreshBackpack/Bank/All/Sort/FullRefreshAll messages. - spec/binding_spec.lua (+2 tests): equipment-slot fallback when
slotID is nil, SOULBOUND as the default for bound retail items. - spec/helpers/wow_mocks.lua: Add SetOwner no-op to CreateFrame mock
(used by data/tooltip.lua Classic scanTooltip). - data/refresh.lua: Fix shouldWipe parameter count in
bags/RefreshBackpack and bags/RefreshBank handlers. The source had
three underscores (function(_, _, shouldWipe)) but the events
wrapper only passes two args (ctx, value), so shouldWipe was always
nil and the wipe flag was never set. Changed to function(_, shouldWipe).
No production code calls SendMessage with a third arg on these
messages, so the behavior change is safe. - .gitignore: Track .claude/worktrees/ so the stranded worktree subdir
isn't accidentally committed.
Results: 727 -> 798 tests, 0 failures, 0 luacheck warnings.
Coverage (from luacov on the rebased tree): - core/events.lua: 37.0% -> 58.1%
- core/async.lua: 37.8% -> 44.2%
- core/localization.lua: 0% -> 41.7%
- data/tooltip.lua: 33.1% -> 49.1%
- data/groups.lua: 40.2% -> 45.5%
- data/refresh.lua: 35.3% -> 49.1%
- 7 of 33 source files at 0% (was 8; localization now exercised)
- New spec/localization_spec.lua (11 tests): G() returns key for missing
- ci: track test coverage with luacov (#961)
Wire luacov into the test workflow so we can monitor coverage over time.
Adds:- .luacov: project config for luacov. Includes source roots
(util, core, data, animations, integrations, themes), excludes
spec/, libs/, and .libraries/ (third-party and test code), and
writes reports to the project root. - .github/workflows/test.yml: installs luacov via luarocks, runs
busted --coverage, generates the luacov report, and uploads it
as a build artifact (luacov-report, retained 14 days). - .gitignore: ignore the generated luacov.report.out and
luacov.stats.out files.
To reproduce locally:
busted --coverage
luacov
Current coverage (727 tests): 25/33 source files exercised, lowest
uncovered are data/items.lua (22.6%), data/tooltip.lua (33.1%),
data/categories.lua (34.5%), data/refresh.lua (35.3%).
- .luacov: project config for luacov. Includes source roots
- test: add coverage for equipmentsets, resize, database, refresh + fix two bugs (#960)
- Add equipmentsets_spec (22 tests): C_EquipmentSet API, version gating, GetItemSets
- Add resize_spec (11 tests): resize handle creation and script wiring
- Add database_spec (135 tests): getters/setters, categories, groups, profiles, import/export
- Add database_migration_spec (4 tests): Migrate function edge cases
- Add refresh_spec (24 tests): RequestUpdate state machine, debounce, sort, combat gating
- Add ResetModuleStub helper to resolve module stub/load ordering conflicts
- Add C_EquipmentSet, EquipmentManager_UnpackLocation, EquipmentManager_GetLocationData to wow_mocks
- Fix Bug: operator precedence crash in Migrate() when itemsPerRow is nil (database.lua:1021)
- Fix Bug: leaked debounce timer during sort request (refresh.lua:90-98)
687 -> 692 tests, 0 failures, 0 luacheck warnings
- fix(intervaltree): wire min/max maintenance and range-query pruning (#959)
- fix: add nil-guard to section sort functions to prevent crash
SortSectionsAlphabetically, SortSectionsBySizeDescending, and
SortSectionsBySizeAscending all crashed when passed nil sections
because SortSectionsByPriority returned (false, false) for nil args
but the callers then proceeded to access a.title:GetText().
Addedif not a or not b then return false endat the top of each
function. Added tests covering nil args for all three sort modes.
Bug found by expanding the test suite -- the original nil-section test
exposed this crash.
Co-Authored-By: Claude Opus 4.6 (1M context) noreply@anthropic.com - more tests (#942)
- added ignores (#940)
- test: add data layer tests with shared MockData factory (#944)
Shared mock infrastructure:
- spec/helpers/mock_data.lua: canonical MockData.ItemData, EmptySlot,
Item, Section, Frame factories - Updated sort, stacks, search specs to use shared factory
- Added wow_mocks: securecallfunction, CopyTable, geterrorhandler,
InCombatLockdown, GetFramerate, format, strtrim, C_Item stubs
New test suites (115 tests): - search_spec (38): full query pipeline, ngrams, comparisons, NOT/AND/OR
- stacks_spec (21): stack management, root promotion
- events_spec (14): message register/send, CatchUntil
- binding_spec (14): bound/unbound types, retail SOULBOUND/ACCOUNT
- slots_spec (16): SlotInfo changeset tracking, empty slot sorting
- tooltip_spec (12): cache CRUD, retail C_TooltipInfo extraction
- categories_spec (30): CRUD, ephemeral/persistent merge, rename with
sub-category cleanup, visibility, search categories, priority sort,
RegisterCategoryFunction, dynamic categories - async_spec (12): Do/Each/Batch/Until/Chain/AfterCombat, coroutine
resume via controllable C_Timer
Bugs found and fixed: - stacks.HasItem returned nil instead of false
Total: 396 tests passing.
- test: add groups, categories, async, deep items tests + fix GetCategory crash (#945)
New test suites:
- groups_spec (21 tests): CRUD, default groups, category assignment,
CategoryBelongsToGroup logic, active group management - categories_spec (30 tests): create/delete/rename, ephemeral vs persistent
merge, visibility, search categories, priority sort, RegisterCategoryFunction - async_spec (12 tests): Do/Each/Batch/Until/Chain/AfterCombat, coroutine
resume via controllable C_Timer - items_spec expanded (35 → 70 tests): ItemChanged, GetCategory full pipeline,
GetGroupBySuffix, findBestFit stack merging, search cache operations
Shared infrastructure: - spec/helpers/mock_data.lua: MockData.ItemData, EmptySlot, Item, Section, Frame
- Updated sort, stacks, search specs to use shared MockData factory
- Added wow_mocks: strsplit max param, strsplittable, C_NewItems, string.split,
InCombatLockdown, GetFramerate, format, strtrim, Enum.ItemClass
Bug #4 found and fixed: items:GetCategory crashed when data.kind was nil
because self.searchCache[data.kind] was accessed before the nil guard.
Moved the guard above the cache access.
Total: 495 tests, 4 bugs found and fixed.
- fix(intervaltree): wire min/max maintenance and range-query pruning
Previously the IntervalTree had vestigial min/max fields set once at
node creation but never propagated upward. Every node always had
min = max = node.value, which meant the tree degraded to a plain BST
doing full O(n) scans for every range query.
Changes:
util/trees/intervaltree.lua:
- InsertRecursive: call RecalculateMinMax(node) after every insertion
to bubble min/max up the insertion path to root. - New RecalculateMinMax(node): set node.min/max from own value + children.
- New RecalculateMinMaxPathToRoot(startNode): walk up via FindParent
recalculating every ancestor. Used after structural tree changes. - New CollectSubtree(node, result): collect all nodes in a subtree
into a result list (used for bulk-addition during pruning). - LessThanRecursive / LessThanEqualRecursive / GreaterThanRecursive /
GreaterThanEqualRecursive: replaced the always-visit-both-children
pattern with proper interval-tree pruning:- If the entire subtree qualifies (min/max pass), CollectSubtree
and return. - If the entire subtree is excluded (min/max fail), return
immediately. - Otherwise partial: check the current node and recurse both
children.
- If the entire subtree qualifies (min/max pass), CollectSubtree
- RemoveNode: rewritten to handle all four BST deletion cases (leaf,
left-only child, right-only child, two children via inorder
predecessor replacement) with correct min/max recalculation after
each structural change. - Removed the unused RemoveNodeRecursive function.
spec/intervaltree_spec.lua: - Added min/max on insert test suite: root tracking, deep subtrees,
balanced trees, interior node ranges, duplicate-insert stability,
right-heavy and left-heavy shapes. - Added min/max on remove test suite: leaf removal updating ancestors,
min/max leaf removal, root removal via predecessor replacement,
non-leaf-with-two-children removal, sequential removal chain. - Expanded LessThan tests: strict exclusion at boundary, larger trees
with gaps, pruning of right subtree when left qualifies, between
interior nodes. - Expanded LessThanEqual tests: larger trees, boundary exact match.
- Expanded GreaterThan tests: strict exclusion at boundary, larger trees
with gaps, pruning of left subtree when right qualifies, between
interior nodes. - Expanded GreaterThanEqual tests: larger trees, boundary exact match.
- New combined range query tests: complementarity of LessThan/GreaterThan,
gap-between-nodes correctness. - New data correctness tests: slotkey preservation through range queries,
data preservation through insert/remove cycles. - Added range query correctness after remove-and-reinsert.
Co-authored-by: Claude Opus 4.6 (1M context) noreply@anthropic.com - fix: add nil-guard to section sort functions to prevent crash
- test: add groups, categories, async, deep items tests + fix GetCategory crash (#945)
New test suites:- groups_spec (21 tests): CRUD, default groups, category assignment,
CategoryBelongsToGroup logic, active group management - categories_spec (30 tests): create/delete/rename, ephemeral vs persistent
merge, visibility, search categories, priority sort, RegisterCategoryFunction - async_spec (12 tests): Do/Each/Batch/Until/Chain/AfterCombat, coroutine
resume via controllable C_Timer - items_spec expanded (35 → 70 tests): ItemChanged, GetCategory full pipeline,
GetGroupBySuffix, findBestFit stack merging, search cache operations
Shared infrastructure: - spec/helpers/mock_data.lua: MockData.ItemData, EmptySlot, Item, Section, Frame
- Updated sort, stacks, search specs to use shared MockData factory
- Added wow_mocks: strsplit max param, strsplittable, C_NewItems, string.split,
InCombatLockdown, GetFramerate, format, strtrim, Enum.ItemClass
Bug #4 found and fixed: items:GetCategory crashed when data.kind was nil
because self.searchCache[data.kind] was accessed before the nil guard.
Moved the guard above the cache access.
Total: 495 tests, 4 bugs found and fixed.
- groups_spec (21 tests): CRUD, default groups, category assignment,
- test: add data layer tests with shared MockData factory (#944)
Shared mock infrastructure:- spec/helpers/mock_data.lua: canonical MockData.ItemData, EmptySlot,
Item, Section, Frame factories - Updated sort, stacks, search specs to use shared factory
- Added wow_mocks: securecallfunction, CopyTable, geterrorhandler,
InCombatLockdown, GetFramerate, format, strtrim, C_Item stubs
New test suites (115 tests): - search_spec (38): full query pipeline, ngrams, comparisons, NOT/AND/OR
- stacks_spec (21): stack management, root promotion
- events_spec (14): message register/send, CatchUntil
- binding_spec (14): bound/unbound types, retail SOULBOUND/ACCOUNT
- slots_spec (16): SlotInfo changeset tracking, empty slot sorting
- tooltip_spec (12): cache CRUD, retail C_TooltipInfo extraction
- categories_spec (30): CRUD, ephemeral/persistent merge, rename with
sub-category cleanup, visibility, search categories, priority sort,
RegisterCategoryFunction, dynamic categories - async_spec (12): Do/Each/Batch/Until/Chain/AfterCombat, coroutine
resume via controllable C_Timer
Bugs found and fixed: - stacks.HasItem returned nil instead of false
Total: 396 tests passing.
- spec/helpers/mock_data.lua: canonical MockData.ItemData, EmptySlot,
- added ignores (#940)
- more tests (#942)
- fix: add nil-guard to section sort functions to prevent crash (#943)
SortSectionsAlphabetically, SortSectionsBySizeDescending, and
SortSectionsBySizeAscending all crashed when passed nil sections
because SortSectionsByPriority returned (false, false) for nil args
but the callers then proceeded to access a.title:GetText().
Addedif not a or not b then return false endat the top of each
function. Added tests covering nil args for all three sort modes.
Bug found by expanding the test suite -- the original nil-section test
exposed this crash.
Co-authored-by: Claude Opus 4.6 (1M context) noreply@anthropic.com - test: add unit test framework with modular mocks and comprehensive utility tests (#941)
Refactor test infrastructure into spec/helpers/ and add comprehensive tests
for all utility modules and core supporting code:
Mock foundation:- spec/helpers/wow_mocks.lua: WoW globals (CreateFrame, C_Timer, securecallfunction,
CopyTable, string/table funcs) - spec/helpers/addon_loader.lua: BetterBags addon creation, LoadBetterBagsModule(),
StubBetterBagsModule() helpers - spec/setup.lua refactored to delegate to helpers
Tests added (260 total): - QueryParser: Lexer, Parser, Query integration (43 tests)
- Serialization: round-trips, Base64, DeepCopy (22 tests)
- IntervalTree: insert, query, remove (26 tests)
- Sort: item/section comparators, priorities (45 tests)
- Context: Set/Get, Cancel, Copy, Timeout (28 tests)
- Pool: Acquire/Release/reuse cycle (8 tests)
- Color: tier breakpoints, dynamic maxIlvl (9 tests)
- Bucket: debounce, controllable timer mock (7 tests)
- WindowGroup: show/hide toggle, mutual exclusion (7 tests)
- MovementFlow: context detection, priority ordering (11 tests)
- Stacks: add/remove, root promotion, count tracking (21 tests)
- Events: message register/send, CatchUntil, SendMessageIf (14 tests)
Bug found: stacks.HasItem returned nil instead of false for items not in
a stack (stackinfo.slotkeys[key] is nil, not false). Fixed.
- spec/helpers/wow_mocks.lua: WoW globals (CreateFrame, C_Timer, securecallfunction,
- feat: detect PatchWerk conflict and show undismissable disable prompt (#934)
PatchWerk globally patches WoW API functions in a way that breaks all addons,
even those it is not configured to patch. Added a deferred check in applyCompat()
that fires 2 seconds after OnEnable to allow for delayed addon loading.
If PatchWerk is detected via C_AddOns.IsAddOnLoaded, a StaticPopupDialogs entry
(BETTERBAGS_PATCHWERK_CONFLICT) is registered and shown immediately. The popup:- Has no cancel button and hideOnEscape = false (cannot be dismissed)
- Has a single 'Disable & Reload' button that calls C_AddOns.DisableAddOn and ReloadUI
- Uses timeout = 0 so it never auto-closes
Updated core/README.md to document the applyCompat compat check pattern and both
the ElvUI skin and PatchWerk checks.
Co-authored-by: Claude Sonnet 4.6 noreply@anthropic.com
- fix: initialize itemList before iteration to prevent crash (#929)
When iterating over a category's itemList with pairs(), a nil itemList would cause a crash. This ensures that the itemList is initialized to an empty table before iteration in database and category modules. It also includes several missing nil guards in categories.lua. - fix: nil-guard stale slotkeys in deferred draw pipeline (#927)
When events:SendMessageLater defers a bag draw by one frame via
C_Timer.After(0), a concurrent BAG_UPDATE_DELAYED or wipe=true refresh
can replace the SlotInfo object before the draw fires. Any slotkeys
built from the previous changeset then return nil from
GetItemDataFromSlotKey, causing the 'data must be provided' crash in
SetItemFromData.
Guards added at all call sites that dereference the result:- frames/item.lua SetItem: return early and log if data is nil
- views/gridview.lua CreateButton: return false if item is nil
- views/gridview.lua ClearButton: skip drawOnClose assignment if item is nil
- views/gridview.lua ReconcileWithPartial: return if rootItem is nil;
nil-check childData before comparing itemStackCount fields
Also documents the race condition pattern in .context/patterns-state.md
for future reference.
- chore: remove stale context file reference from CLAUDE.md (#926)
The @.context/wow-addon-architect.md reference no longer exists on disk,
causing ENOENT errors when the file is loaded. Removed the stale line;
all remaining @.context/ references point to valid files. - fix: enforce bank min width to match bottom slots panel width (#923)
The bank tab slots panel is anchored below the bag frame and can be
wider than the item grid, causing it to overflow past the right edge.
Three-point fix:- bankslots.lua Draw(): after setting the panel width, immediately
clamp bagFrame width to be >= panelWidth. Covers the first draw and
any redraws from BANK_TAB_SETTINGS_UPDATED /
PLAYER_ACCOUNT_BANK_TAB_SLOTS_CHANGED while the panel is shown. - gridview.lua / bagview.lua: added a slots panel check alongside the
existing bag.tabs.width floor. The minimum content width back-
calculates from the panel frame width using the same inset/scrollbar
constants so that bagWidth (w + insets + scrollbar) equals the panel
frame width exactly.
The bag.slots:IsShown() guard means this only fires when the panel is
active (bank open), leaving backpack rendering unaffected.
- bankslots.lua Draw(): after setting the panel width, immediately
- fix: eliminate initial render flash for bank tab display mode (#921)
- fix: prevent initial render flash when show bank tabs is enabled
When GetShowBankTabs() is true, the bank window previously rendered the
full grouped bag for a brief moment before swapping to the Blizzard tabs
view — a visible flash of unstyled content on /reload or fresh session.
Pre-configure blizzardBankTab and the panel's selectedBagIndex in
OnShow() before any render occurs, mirroring the backpack's 'show bags'
pattern. The first Bank:Draw() (issued by init.lua) now already has the
correct tab filter applied, so no grouped content is ever shown.
SelectFirstTab() now checks selectedBagIndex and skips the redundant
re-render when the tab is already pre-selected by OnShow. Both the fade
and no-fade paths in OnShow also set BankPanel's active bank type to
match the pre-selected first tab, so right-click item deposits go to the
correct bank type from the start.
Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com - fix: eliminate initial render flash by pre-setting blizzardBankTab in BANKFRAME_OPENED
The previous fix set blizzardBankTab in OnShow(), but this was too late.
The event sequence is:
BANKFRAME_OPENED → BAG_UPDATE_DELAYED → items:RefreshBank() → items/RefreshBank/Done → Bank:Show() + Bank:Draw(ctx, slotInfo)
The slotInfo passed to Bank:Draw() is built during items:RefreshBank(), which
reads blizzardBankTab to filter the bag list. Since Bank:Show() (which called
OnShow()) runs after slotInfo is already built, the first render always used
unfiltered slotInfo — causing the flash of all grouped bank items.
Fix: set blizzardBankTab in the BANKFRAME_OPENED handler in data/items.lua,
before BAG_UPDATE_DELAYED triggers items:RefreshBank(). The buttons array in
bankSlots is populated at panel creation (addon init), so buttons[1].bagIndex
is available immediately when BANKFRAME_OPENED fires.
The OnShow() pre-configuration block is kept for button visual state (SetSelected
and selectedBagIndex), which enables the SelectFirstTab() skip optimization — the
fade-in OnFinished hook checks selectedBagIndex and skips the redundant re-render
when the first tab is already pre-selected.
Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com
Co-authored-by: Claude Sonnet 4.6 noreply@anthropic.com - fix: prevent initial render flash when show bank tabs is enabled
- feat: move bank slots panel to bottom with hidden tabs, centred icons, and persisted visibility (#920)
- feat: move bank slots panel to bottom with hidden tabs and relocated title
When the bank slots panel is toggled open:
- Reanchors the panel to sit at the bottom of the bank window (TOPLEFT
of panel → BOTTOMLEFT of bag frame, -2px gap) rather than floating
above it, placing the Blizzard bank tab icons in the space normally
occupied by the group tabs. - Completely hides the group tabs frame (rather than just disabling
clicks) so the bank slots panel takes that visual position cleanly.
Saves tabsWereShown on the panel so restoration is correct even if
tabs were already hidden for another reason. - Hides the window title from its normal top-bar position via the new
themes:ToggleTitleContainer() helper and instead shows an external
yellow FontString label anchored just below the bank slots panel,
keeping the window title visible without cluttering the top bar.
When the panel is closed (fadeOutGroup OnFinished): - Restores the panel anchor to BOTTOMLEFT → TOPLEFT, 0, 14 (original
theme position above the bag frame). - Restores group tab visibility only if tabs were shown before opening.
- Restores the top-bar title and hides the external label.
Added themes:ToggleTitleContainer(frame, shown) to themes/themes.lua: - Iterates frame children looking for TitleContainer (Default theme /
Blizzard DefaultPanelTemplate) or a FontString named 'title' (used
by SimpleDark, GW2, and ElvUI themes). - Works across all built-in themes without per-theme changes.
Updated frames/README.md and themes/README.md to document the new
toggle behavior, the bagFrame/titleLabel/titleText/tabsWereShown fields,
and the ToggleTitleContainer API.
Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com
- refactor: eliminate Bank Tabs window title instead of toggling it
Replaces the previous approach of dynamically hiding/restoring the bag
window title and rendering an external label below the panel. The Bank
Tabs panel is now registered with an empty string title so no title text
ever appears in any theme's decoration — no toggling required.
Removes:
themes:ToggleTitleContainer()function and all call sites- External
titleLabel/titleTextframe and FontString fontsmodule import in bankslots.lua (no longer needed)
The main bag window title is left completely untouched. The other layout
changes (group tabs hidden while panel is open, panel anchored at bottom)
are preserved as-is.
Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com
- fix: vertically centre slot icons in Bank Tabs panel by removing title gap
The Bank Tabs flat window has no title, but DefaultPanelFlatTemplate still
renders a visible TitleContainer bar, pushing content down ~18 px and
creating an asymmetric gap at the top. Three co-ordinated fixes:
- themes/default.lua: hide TitleContainer when the registered title is empty
so the visual bar is not present. - frames/bankslots.lua: change the content TOPLEFT anchor from -30 to -12,
matching the existing 12 px bottom inset (BAG_LEFT_INSET-style padding). - frames/bankslots.lua: adjust SetHeight from h+42 to h+24 (12 top + 12
bottom) so the frame height stays consistent with the new symmetric inset. - frames/README.md: document the TitleContainer hide behaviour and symmetric
padding.
Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com
- feat: persist bank tabs visibility setting and restore on bank open
When the bank is opened, check the newshowBankTabsdatabase setting
to decide whether to show the bank tab slots panel or the group tabs:
- core/constants.lua: add
showBankTabs = falseto DATABASE_DEFAULTS so
the setting round-trips correctly via AceDB across sessions. - core/database.lua: add GetShowBankTabs / SetShowBankTabs accessors.
- frames/contextmenu.lua: the "Show Bags" context menu entry now reads and
writes the persistent setting for the bank, so the check-mark correctly
reflects the saved state rather than live frame visibility. - bags/bank.lua:
- GenerateGroupTabs: suppress the group tab bar when showBankTabs is
enabled, preventing both UI elements from appearing simultaneously on
open. - OnShow (fade-animation path and direct path): restore the bank slots
panel automatically when the setting is enabled, so re-opening the
bank returns to the same mode the user last chose.
Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com
- GenerateGroupTabs: suppress the group tab bar when showBankTabs is
- fix: prevent group tabs and bank slots panel from showing simultaneously on bank open
Root cause:slots.frameis a child ofbag.frame. Whenbag.frame:Hide()is
called on bank close,slots.frame:IsVisible()becomes false but its own
IsShown()state remainstrue. On the next bank open,bag.frame:Show()would
immediately re-showslots.frameas a visible child — and ifGenerateGroupTabs
ran withGetShowBankTabs()=falseat that moment (e.g. because the GW2 theme's
"Show Bags" button bypassed the DB setter), both the group tabs and the bank
slots panel would be visible simultaneously.
Fix 1 (bank.lua): Inbank.proto:OnHide(), afterbag.framehides (both in
the fade-out callback and the non-fade path), callself.bag.slots.frame:Hide()
explicitly to resetslots.frame:IsShown()tofalse. This ensures the frame
cannot auto-reappear when the bank is reopened via the parent-show mechanism.
Fix 2 (gw2.lua): The GW2 theme "Show Bags" side button now calls
database:SetShowBankTabs(true/false)when toggling the bank slots panel,
matching the behaviour of the standard context menu toggle. AddedDatabase
andConstantsmodule imports to support this.
Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com
Co-authored-by: Claude Sonnet 4.6 noreply@anthropic.com - feat: move bank slots panel to bottom with hidden tabs and relocated title
- feat: disable bottom tabs while bank slots panel is open (#919)
Add SetTabsDisabled(disabled) to tabFrame so the bank slots panel can
make the bottom group-tabs non-interactive while it is visible.- tabs.lua: new SetTabsDisabled method dims the tab bar to 50% alpha and
sets a tabsDisabled flag; OnClick and OnMouseDown handlers return early
when the flag is set, blocking tab switching and drag-to-reorder - bankslots.lua: call SetTabsDisabled(true) on Show() entry and
SetTabsDisabled(false) in fadeOutGroup.OnFinished so tabs are re-enabled
only after the fade-out animation completes - frames/README.md: document the new disable state and updated Show/Hide
behaviour for the bank slots panel
Co-authored-by: Claude Sonnet 4.6 noreply@anthropic.com
- tabs.lua: new SetTabsDisabled method dims the tab bar to 50% alpha and
- feat: remove obsolete purchase bank tab buttons and toggle flag (#918)
Removes the 'Purchase Bank Tab' and 'Purchase Warbank Tab' buttons that
were rendered as persistent tab buttons on the bank frame. These are no
longer needed with the new bank system where Blizzard handles purchasing
natively.
Also removes the ENABLE_WARBANK_PURCHASE_TAB dead-code flag that was
already hardcoded to false as a placeholder, along with the associated
tab ID -1/-2 handling in SortTabsByID, stale comment in contextmenu.lua,
and the purchase-button documentation line in README.md. - docs: split patterns.md into topic files and fix CI deps step (#914)
- Condensed .context/patterns.md into a lightweight index pointing to
three new focused topic files - Created .context/patterns-taint.md covering taint, protected code,
and secure function rules - Created .context/patterns-ui.md covering UI patterns (forms, tooltips,
mouse wheel propagation, drag-to-reorder, StaticPopup, pooling,
WindowGrouping, color tiers) - Created .context/patterns-state.md covering state management and
architecture (context propagation, behavior vs visual methods, bank
system architecture, cross-version parity, defensive programming,
debugging strategies) - Updated CLAUDE.md to @-include all new pattern files and improved
Pattern Learning routing guidance - Updated .github/workflows/test.yml to cache ./.libraries alongside
./libs so CI restores all dependencies before running tests
Co-authored-by: Claude Sonnet 4.6 noreply@anthropic.com
- Condensed .context/patterns.md into a lightweight index pointing to
- fix: separate bank slot view and purchase buttons into distinct frames (#917)
- fix: use BankPanelPurchaseButtonScriptTemplate for bank slot purchase buttons
Create bank slot purchase buttons with BankPanelPurchaseButtonScriptTemplate so
that Blizzard's protected mixin handles the purchase flow. Set the overrideBankType
attribute from slotInfo.bankType (Enum.BankType.Character or Enum.BankType.Account)
so the mixin knows which bank to purchase a tab for. Delegate unpurchased left-clicks
to BankPanelPurchaseTabButtonMixin.OnClick(clickedFrame) instead of calling
StaticPopup_Show directly, keeping purchase logic in Blizzard's protected handler.
Add BankPanelPurchaseTabButtonMixin to .luacheckrc globals. - fix: separate bank slot view and purchase buttons into distinct frames
Split each bank slot into two mutually exclusive sub-buttons inside a
container frame:
- viewButton: a plain Button shown when the slot is purchased; handles
left-click (tab selection) and right-click (tab config). OnClick is
scoped to this button only. - purchaseButton: a Button created with BankPanelPurchaseButtonScriptTemplate
shown when the slot is unpurchased; opens the Blizzard bank tab purchase
dialog natively via the template mixin without any OnClick override.
This replaces the previous approach of applying
BankPanelPurchaseButtonScriptTemplate to a single button and trying to
proxy OnClick through BankPanelPurchaseTabButtonMixin, which violated
WoW's secure-call restrictions. A shared showSlotTooltip closure is
registered on both sub-buttons so hovering either one shows the correct
tooltip.
Also removes BankPanelPurchaseTabButtonMixin from .luacheckrc globals
since it is no longer referenced anywhere in the codebase.
Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com
Co-authored-by: Claude Sonnet 4.6 noreply@anthropic.com - fix: use BankPanelPurchaseButtonScriptTemplate for bank slot purchase buttons
- small claude.md update (#913)
- fix: sort grid sections correctly on bank open and tab switch (#912)
When opening the bank or switching tabs the section sort was silently
skipped. The root cause is a context-boundary gap in the refresh
pipeline:- SwitchToGroup / SwitchToBlizzardTab call bag:Wipe(ctx), where ctx
carries wipe=true and triggers a bags/RefreshBank message. - The bags/RefreshBank handler calls refresh:RequestUpdate() without
forwarding the wipe flag. - ExecutePendingUpdates creates a brand-new context (context:New)
that has no wipe flag. - GridView receives this new context, ctx:GetBool('wipe') is false,
and the sort block is skipped.
Fix: add a view.sortRequired boolean flag on the long-lived view object
that is set to true inside Wipe() and cleared inside GridView() after
the sort runs. GridView now sorts whenever ctx:GetBool('wipe') OR
view.sortRequired is true, so the sort fires regardless of which
context triggered the rebuild.
Changed files:
- views/gridview.lua: set sortRequired=true in Wipe, check it in
GridView sort guard, initialize to false in NewGrid - views/README.md: document the sortRequired flag
- .context/patterns.md: add pattern describing the context-boundary
gap solution for future reference
- SwitchToGroup / SwitchToBlizzardTab call bag:Wipe(ctx), where ctx
- ci: install addon deps via install-deps.sh before running tests (#911)
Add three new steps to the test workflow that run before Lua setup:- Install SVN — runs 'apt-get update && apt-get install -y subversion'
so that install-deps.sh can fetch libraries hosted on SVN (e.g.
LibStub, AceAddon-3.0). The update call prevents failures on runners
with a stale package index. - Cache addon dependencies — uses actions/cache@v4 keyed on the SHA of
install-deps.sh. When the script is unchanged the cached ./libs
directory is restored and subsequent git-clone/svn-checkout guards in
the script skip re-fetching, keeping CI fast. When install-deps.sh
changes the cache misses and a fresh fetch runs. - Install addon dependencies — executes 'bash install-deps.sh' to
clone/checkout LibStub, AceAddon-3.0 and any other libraries the
addon requires so they are present before tests start.
Co-authored-by: Claude Sonnet 4.6 noreply@anthropic.com
- Install SVN — runs 'apt-get update && apt-get install -y subversion'
- fix: remove trailing whitespace in core/database.lua (#910)
Two blank lines inside DB:Migrate() contained trailing spaces (lines 1079 and 1087), which caused luacheck to report whitespace warnings. Replacing them with clean empty lines brings luacheck to 0 warnings / 0 errors across all 110 files. - Update bank slots frame logic and functionality (#909)
- feat: Follow-up fixes for the recently completed bank top tab s...
Automated commit generated by the Nanomite code tool.
Prompt:
Follow-up fixes for the recently completed bank top tab selection feature: 1) The '+' button for purchasing a new tab needs to be the same size as every other button. Make it bigger so it matches. 2) When configuring a tab via right click, changing the properties works, but clicking the icon to change the icon in the blizzard config does nothing (no error, just nothing happens). Do a deep dive on this one, look at Blizzard code if you have to, and fix it so the icon can be changed. 3) When mo... - feat: Make the green plus buttons inside the unowned tab slots ...
Automated commit generated by the Nanomite code tool.
Prompt:
Make the green plus buttons inside the unowned tab slots much larger so they look proportional to the rest of the buttons. Currently, they render as tiny green plus signs in the middle of a normal-sized empty slot background. Fix this in the current branch. - feat: Clarification on the
+button size: It's not a font iss...
Automated commit generated by the Nanomite code tool.
Prompt:
Clarification on the+button size: It's not a font issue, it's the actual icon size. The+icon border/texture is smaller than the other tab icons (like the question mark icons). Please adjust the frame/texture/border bounds to match the exact dimensions of the standard tab icons, rather than just scaling up a font. - feat: 1) texture is fine (if not better), so keep that. 2) i wa...
Automated commit generated by the Nanomite code tool.
Prompt:
- texture is fine (if not better), so keep that. 2) i want you to go back and look at icon size, it is absolutely and completely not correct, it's still too small. i think we had a similar problem with the item button textures as well, check the resize code for item buttons.
- feat: remove the border entirely. just the icon as a button. th...
Automated commit generated by the Nanomite code tool.
Prompt:
remove the border entirely. just the icon as a button. the button is still TOO SMALL. - feat: The previous execution failed because the PR creation wra...
Automated commit generated by the Nanomite code tool.
Prompt:
The previous execution failed because the PR creation wrapper ONLY detects and pushes STAGED changes, not already committed changes. Please rungit reset --soft HEAD~1(or however many commits you need to send back to the staging area) so that your modifications toframes/bankslots.luaand other files are in the staging area when your script finishes. Do not run git commit.
- feat: Follow-up fixes for the recently completed bank top tab s...
- feat: Implement a "show bags" style option for the bank and war... (#908)
Automated commit generated by the Nanomite code tool.
Prompt:
Implement a "show bags" style option for the bank and warbank. Requirements: 1) Add a top slide-out window similar to the backpack's "show bags" option. 2) The slots across the top represent all possible tabs for the bank, left to right, in increasing order, bank to warbank. 3) Slots that we don't own should have a green '+' right in the middle of them to denote they can be purchased. 4) When showing the top window, automatically "select" the very first tab. 5) When a tab is selected, only ... - feat: Investigate and fix an issue where mouse wheel scrolling ... (#907)
Automated commit generated by the Nanomite code tool.
Prompt:
Investigate and fix an issue where mouse wheel scrolling in large frames (like the bank frame) is captured by child frames (like grids and item buttons) and not passed down to the scrollable container. Ensure you can scroll anywhere in the frame. Look into passing the mouse wheel event from these child frames up to the parent container (using WoW API features like SetPropagateMouseClicks, PropagateKeyboardInput, or by handling OnMouseWheel and explicitly passing it to the parent). If it's not... - feat: The free space count in the newly redone bank tabs is inc... (#906)
Automated commit generated by the Nanomite code tool.
Prompt:
The free space count in the newly redone bank tabs is incorrect. When selecting any bank tab, it should show the sum of all bank tabs' free space. When selecting any warbank tab, it should show the sum of all warbank tabs' free space. Currently, it shows the same free space count for both when flipping between the warbank and bank. Please investigate and fix this issue so the free space count is calculated and displayed correctly for the respective bank types. - clean up old files (#905)
- feat: Fix several issues related to group/tab management in Bet... (#904)
Automated commit generated by the Nanomite code tool.
Prompt:
Fix several issues related to group/tab management in BetterBags: 1. Rename/Delete Lua Error: Renaming a group via the right-click menu on a backpack tab throwsattempt to index field '?' (a nil value)indatabase.lua:635. The locals (kind = 21,groupID = "Dee") show that the arguments passed toRenameGroup(and likelyDeleteGroup) are swapped or shifted (e.g., passing the group ID as thekind). Fix theRenameGroupandDeleteGroupcalls inbags/backpack.lua(and `bags/... - feat: We have a bug in BetterBags related to dragging category ... (#903)
Automated commit generated by the Nanomite code tool.
Prompt:
We have a bug in BetterBags related to dragging category headers (sections) onto group tabs, caused by the recent tab changes. When dropping a category header onto a group tab, it produces this Lua error: ``` 1x BetterBags/core/database.lua:635: attempt to index field '?' (a nil value) [BetterBags/core/database.lua]:635: in function 'GetGroup' [BetterBags/data/groups.lua]:104: in function 'IsDefaultGroup' [BetterBags/frames/section.lua]:431: in function 'PerformDrop' [BetterBags/frames/sectio... - feat: In BetterBags, update the bank tab sorting and drag-and-d... (#902)
Automated commit generated by the Nanomite code tool.
Prompt:
In BetterBags, update the bank tab sorting and drag-and-drop logic. 1. Sort Order: The sort order for the bank tabs must be exactly:Bank(static tab), followed by all user-created Bank tabs, thenWarbank(static tab), followed by all user-created Warbank tabs, and finally the+(create) tab at the very end. 2. Drag-and-Drop Constraints: When dragging tabs to reorder them, constrain the drag-and-drop so that Bank tabs can only be dropped within the Bank tab area, and Warbank tabs can on... - feat: The user reported that after successfully creating a new ... (#901)
- feat: The user reported two bugs with the 'Create New Tab' popu...
Automated commit generated by the Nanomite code tool.
Prompt:
The user reported two bugs with the 'Create New Tab' popup (triggered by the '+' button): 1) In the bank, the popup is not sized correctly to fit the dropdown menu (it's cut off or too small). 2) In the backpack, the dropdown is being rendered in the popup, but it should not be shown at all for the backpack. Please fix the sizing issue for the bank popup so it accommodates the dropdown, and ensure the dropdown is completely hidden/removed when creating a tab in the backpack. Commit the change... - feat: The user tested the PR. The fix to hide the dropdown in t...
Automated commit generated by the Nanomite code tool.
Prompt:
The user tested the PR. The fix to hide the dropdown in the backpack worked perfectly. However, the bank popup is still too small and the layout is broken (the dropdown doesn't fit and overlaps/gets cut off). The previous agent removed theC_Timer.After(0)which likely caused Blizzard'sStaticPopup_Showlayout engine to overwrite the customSetHeightand button repositioning. Please fix the bank popup sizing. You will likely need to useC_Timer.After(0)to apply the size and position ... - feat: The user has decided that repurposing Blizzard's
StaticP... Automated commit generated by the Nanomite code tool. Prompt: The user has decided that repurposing Blizzard'sStaticPopupfor the 'Create New Group/Tab' dialog is a hack, and modifying shared pool frames could break other addons. Please completely remove theStaticPopupDialogs` implementation for creating a tab/group (both the Backpack and Bank versions, as well as the hacky OnShow/OnHide/C_Timer layout adjustments). Instead, build a fully custom, standalone frame (a custom popup) within BetterBags for this purpose. Requirements for the custom popup... - feat: The user reported a Lua error when clicking the '+' tab i...
Automated commit generated by the Nanomite code tool.
Prompt:
The user reported a Lua error when clicking the '+' tab in the bank to open the new custom dialog:1x BetterBags/frames/groupdialog.lua:96: attempt to index field 'frame' (a nil value) [BetterBags/frames/groupdialog.lua]:96: in function 'Show' [BetterBags/bags/bank.lua]:334: in function 'ShowCreateGroupDialog'Looking at the locals in the error,self.enabledState = falsefor theGroupDialogmodule. This meansOnEnablenever ran, soself.framewas never created. Please fix `fra... - feat: The user reported that after successfully creating a new ...
Automated commit generated by the Nanomite code tool.
Prompt:
The user reported that after successfully creating a new tab/group using the new dialog, the physical tab does not render in the UI immediately. The addon switches to the new tab "under the hood", but the UI is not updated to show the new tab until a/reload. Please investigate the callbacks insideShowCreateGroupDialogin bothbags/bank.luaandbags/backpack.lua. You likely need to trigger a UI refresh or update the tabs frame (e.g., triggering a full refresh event, or calling the ap...
- feat: The user reported two bugs with the 'Create New Tab' popu...
- feat: Refactor bank system to use virtual tabs (#900)
- Refactor bank system to use virtual tabs/groups
- Replaced 1:1 physical bank tabs with virtual groups
- Created default 'Bank' and 'Warbank' tabs
- Added '+' button to Bank window with dropdown picker for Bank/Warbank
- Migrated legacy database configurations and existing groups
- Updated category dragging and dropping to properly handle default tabs
- Removed 'Show Character Bank Tabs' configuration
- Implemented maximum bag height capping (90% of screen height) and enabled grid scrolling
- feat: Fundamentally rework the bank system in BetterBags to use...
Automated commit generated by the Nanomite code tool.
Prompt:
Fundamentally rework the bank system in BetterBags to use the virtual tab/group system currently used by the backpack. Here are the specific requirements for this major refactor: 1. Replace the current 1:1 rendering of regular bank tabs. Instead, create two static, un-deletable tabs in the bank window: 'Bank' and 'Warbank'. 2. The 'Bank' and 'Warbank' tabs should render all items and sections for each type of bank respectively. 3. Add a '+' button to the bank window to create new tabs. When c... - Fix Bank/Warbank default tab initialization to prevent overwriting existing user groups
- Removed hardcoded default Bank and Warbank from DATABASE_DEFAULTS.
- Added database migration step to correct existing profiles where user groups were inadvertently merged with Bank/Warbank default fields.
- Migration now cleanly generates Bank/Warbank with unused IDs only if they don't already exist.
- Updated 'isDefault' flag to reliably identify system tabs instead of looking for names 'Bank' and 'Warbank' or hardcoded IDs '2' and '3'.
- Fix remaining virtual bank tab refactor edge cases
- Removed hardcoded default activeGroup for BANK in constants to ensure dynamic assignment via migration logic is respected.
- Refactored 'DeleteGroup' protection and fallback mechanisms in database and groups modules to strictly use 'isDefault' and 'bankType' flags rather than brittle string matching.
- Enhanced profile migration cleanup to robustly scrub incorrectly merged default properties from custom user groups based on property signatures instead of names.
- Resolved carry-over review issues: Removed dead code in refresh logic, eliminated constants mutation in items logic, added missing 'groups/Changed' event handlers, localized missing strings, and added nil fallbacks for deprecated WoW API enums.
- Fix scrollbar overlapping items in bag/bank windows
- Added 14px of extra width padding to the bag container dynamically when the grid content exceeds max height and triggers the scrollbar to appear. This ensures the scrollbar sits on the outer right edge without cutting off the rightmost column of items.
- Address remaining scrollbar and migration edge cases
- Added const.OFFSETS.SCROLLBAR_WIDTH (14px) to replace magic number for scrollbar width.
- Always allocate scrollbar width to the outer frame. This prevents a jarring visual jump when adding/removing items that toggles the scrollbar visibility.
- Fixed indentation in DB:Migrate block.
- Implemented a robust __bankDefaultTabsFixed migration step. Instead of using string matching to detect merged groups, it strips bank properties from all non-Backpack groups securely, letting the module recreate fresh default Bank tabs without conflicting with user group names.
- Localized the default creation strings using L:G('Bank') and L:G('Warbank').
- Refactor group storage to be strictly scoped by bag kind
- Addressed the bug where Backpack windows improperly showed Bank/Warbank default tabs and custom Bank tabs.
- Updated database schema so 'groups', 'groupCounter', and 'categoryToGroup' are strictly scoped by BagKind internally, establishing fully discrete namespaces for Backpack and Bank groups. Group IDs are now safely unique per bag type.
- Developed robust DB:Migrate routine to safely restructure existing global groups into scoped arrays. It carefully unwinds any prior corruption from AceDB default merges and seamlessly ports custom backpack tabs back into the backpack space.
- Refactored core modules (database, groups, tabs, integrations) to require and pass 'kind' arguments to all relevant API methods to ensure context is never ambiguously read from global states.
- Fix critical runtime bugs and migration edge cases in scoped groups refactor
- Resolved a live crash in 'data/items.lua' where 'database:GetGroup' was called without the required 'kind' parameter.
- Fixed 'groups:OnCategoryDeleted' and 'groups:SetActiveGroup' missing the 'kind' parameter, preventing dead code crashes and silently orphaned categories on deletion.
- Fortified the DB:Migrate routine to safely recognize and bypass fresh installs where 'groups' is already scoped, thereby preventing data corruption.
- Eliminated deep merge conflicts with AceDB by definitively removing hardcoded Bank tab definitions from 'const.DATABASE_DEFAULTS'.
- Cleaned up obsolete redundant wrapper function 'GetGroupsByKind' and refactored call sites to use 'GetAllGroups(kind)'.
- Corrected 'groups/Deleted' event listener signatures to properly intercept the newly dispatched 'kind' parameter.
- Fix lua error where groupCounter was left as a number during migration
- Added a catch-all safety check to the migration routine to always verify that 'groupCounter' has been fully migrated into a 'BagKind'-keyed table, rather than relying exclusively on the scoped migration block which might have been skipped for certain profiles.
- Fix isAlreadyScoped logic and resolve carry-over bugs
- Corrected the 'isAlreadyScoped' heuristic in DB:Migrate. Because const.BAG_KIND.BANK is 1, checking for oldGroups[1] erroneously flagged old flat-structure configurations (where group ID 1 is the Backpack group) as already scoped. The logic now strictly verifies that the nested table behaves like a namespace rather than a group object (by checking for the absence of the 'name' field).
- Fixed the two remaining instances of missing 'kind' arguments from prior code reviews: 'groups:OnCategoryDeleted' now iterates through all bag kinds to correctly resolve and clean up orphaned category groups, and 'groups:SetActiveGroup' correctly passes the kind enum down to 'database:GetGroup'.
- Fix nil index error on currentTabFrame in IsTabReorderable
- The tabs:IsTabReorderable function was incorrectly accessing self.currentTabFrame, which is only populated during an active drag event. This caused a lua error on load when it was invoked during initial tab generation by ResizeTabByIndex.
- Updated the function signature of tabs:IsTabReorderable to accept 'kind' directly and refactored call sites to explicitly provide the current bag kind context.
- Fix hardcoded groupCounter default for BAG_KIND.BANK
- Updated 'DATABASE_DEFAULTS.groupCounter[const.BAG_KIND.BANK]' from 2 to 0 in 'core/constants.lua'.
- This resolves a pre-existing issue where the hardcoded value of 2 caused AceDB to deeply merge and overwrite the group counter, conflicting with users who had dynamically assigned IDs after migrating their profiles.
- Disable Warbank purchase button and ensure deterministic tab sorting
- Suppressed the rendering of the 'Purchase Warbank Tab' button by wrapping its logic in an 'if false' block. The underlying logic remains intact for a future PR.
- Strengthened the sorting algorithm in 'frames/tabs.lua' to be fully deterministic. In the highly unlikely event of identical IDs or tabs completely missing IDs, the algorithm now predictably falls back to an alphabetical sort by tab name to prevent random order shifting across reloads.
- Replace 'if false' code smell with a named constant
- Introduced 'ENABLE_WARBANK_PURCHASE_TAB' constant in bags/bank.lua to explicitly disable the Warbank purchase tab feature while making it easily searchable and maintainable for future PRs.
- feat: Run
luacheck spec/and fix any linting errors or warnin... (#899)
Automated commit generated by the Nanomite code tool.
Prompt:
Runluacheck spec/and fix any linting errors or warnings in the files we just created/modified (spec/setup.lua,spec/basic_spec.lua). Make sure thatluacheckpasses completely before finishing. You may need to add some globals to.luacheckrcor use inline luacheck pragmas if they are intentional WoW globals. - feat: We need to start building out the Busted tests for the Be... (#897)
Automated commit generated by the Nanomite code tool.
Prompt:
We need to start building out the Busted tests for the BetterBags WoW addon. The first step is gettingAceAddon-3.0to load in our tests without errors, as it is used everywhere in the addon. Please do the following: 1. Create or update aninstall-deps.shscript to check outAceAddon-3.0(and any of its dependencies, likeLibStubandCallbackHandler-1.0) using thesvn checkoutcommand. Place them in a directory suitable for testing (like alibsor.libsfolder). 2. Set up the ... - chore: add GEMINI.md symlink pointing to CLAUDE.md (#896)
- ci: fix claude-code-action crash (#895)
- ci: fix claude-code-action crash due to invalid allowed-tools arg
- ci: pin claude-code-action to v1.0.51 to bypass ajv schema bug
- ci: remove claude-code-action workflow
- chore: setup busted testing framework (#894)