BetterBags

A total bag replacement for World of Warcraft, successor to AdiBags!

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)
    1. 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.
    2. 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.
    3. 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.
    4. 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:
    1. Strict requirement that any new code changes must be validated and tested using mocks.
    2. Missing WoW API mocks must be derived from Blizzard's official WoW UI source code instead of being guessed or invented.
    3. 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.
  • 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)
  • 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%).
  • 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().
      Added if not a or not b then return false end at 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.
    • 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
  • 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.
  • 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.
  • 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().
    Added if not a or not b then return false end at 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.
  • 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.
  • 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
  • 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/titleText frame and FontString
    • fonts module 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 new showBankTabs database setting
      to decide whether to show the bank tab slots panel or the group tabs:
    • core/constants.lua: add showBankTabs = false to 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
    • fix: prevent group tabs and bank slots panel from showing simultaneously on bank open
      Root cause: slots.frame is a child of bag.frame. When bag.frame:Hide() is
      called on bank close, slots.frame:IsVisible() becomes false but its own
      IsShown() state remains true. On the next bank open, bag.frame:Show() would
      immediately re-show slots.frame as a visible child — and if GenerateGroupTabs
      ran with GetShowBankTabs()=false at 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): In bank.proto:OnHide(), after bag.frame hides (both in
      the fade-out callback and the non-fade path), call self.bag.slots.frame:Hide()
      explicitly to reset slots.frame:IsShown() to false. 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. Added Database
      and Constants module 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: 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
  • 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
  • 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
  • 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:
    1. SwitchToGroup / SwitchToBlizzardTab call bag:Wipe(ctx), where ctx
      carries wipe=true and triggers a bags/RefreshBank message.
    2. The bags/RefreshBank handler calls refresh:RequestUpdate() without
      forwarding the wipe flag.
    3. ExecutePendingUpdates creates a brand-new context (context:New)
      that has no wipe flag.
    4. 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
  • 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:
    1. 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.
    2. 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.
    3. 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
  • 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:
    1. 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 run git reset --soft HEAD~1 (or however many commits you need to send back to the staging area) so that your modifications to frames/bankslots.lua and other files are in the staging area when your script finishes. Do not run git commit.
  • 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 throws attempt to index field '?' (a nil value) in database.lua:635. The locals (kind = 21, groupID = "Dee") show that the arguments passed to RenameGroup (and likely DeleteGroup) are swapped or shifted (e.g., passing the group ID as the kind). Fix the RenameGroup and DeleteGroup calls in bags/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, then Warbank (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 the C_Timer.After(0) which likely caused Blizzard's StaticPopup_Show layout engine to overwrite the custom SetHeight and button repositioning. Please fix the bank popup sizing. You will likely need to use C_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's StaticPopupfor 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 = false for the GroupDialog module. This means OnEnable never ran, so self.frame was 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 inside ShowCreateGroupDialog in both bags/bank.lua and bags/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: 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:
    Run luacheck spec/ and fix any linting errors or warnings in the files we just created/modified (spec/setup.lua, spec/basic_spec.lua). Make sure that luacheck passes completely before finishing. You may need to add some globals to .luacheckrc or 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 getting AceAddon-3.0 to load in our tests without errors, as it is used everywhere in the addon. Please do the following: 1. Create or update an install-deps.sh script to check out AceAddon-3.0 (and any of its dependencies, like LibStub and CallbackHandler-1.0) using the svn checkout command. Place them in a directory suitable for testing (like a libs or .libs folder). 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)