Dibs

A master‑loot tool for Classic Era raids: everyone calls dibs on the gear they want before the raid, so when an item drops the masterlooter instantly sees who's competing — for fast, fair, drama‑free distribution. Works for pugs too.
Back to Files

Dibs-v0.7.1

File nameDibs-Dibs-v0.7.1.zip
Uploader
PmptastyPmptasty
Uploaded
Oct 12, 2026
Downloads
97
Size
1.8 MB
Flavors
Classic TBCClassicForever
File ID
9151146
Type
R
Release
Supported game versions
  • 2.5.6
  • 1.60.1
  • 1.15.9

What's new

Changelog

[v0.7.1] -- random-enchant gear gets its EP

Changed -- release notes for v0.7.1

Release prep, 2026-10-12. A patch bump: everything since Dibs-v0.7.0 is a fix. GUI/Intro.lua's UPDATES now lists v0.7.1 only (whatsnew_v071_suffix under Fixed, and whatsnew_v071_known, v0.7.0's known-issues text unchanged); the whatsnew_v070_* keys are deleted from Locale/enUS.lua. The CurseForge page gains the v0.7.1 block and drops v0.5.0 (five versions kept), and docs/24-test-run-checklist.md names the v0.7.1 key as its example. The player bullet says healer scoring of the spell damage and healing bonuses is not yet checked in game, as the Peer Review entry below requires. README and the page's feature list needed no change: neither claimed random-enchant items were unscored. Self-audit thread d10e58a2: the bullet's first draft named "a ring of the Owl" unchecked; it now names the "of the Owl" and "of the Bear" families, which Peer Review confirmed in ItemDB's suffix table.

Fixed -- random-enchant gear ("of Striking", "of the Owl") shows its EP

Cpthammer88, #dibs 2026-10-12: "dibs does not know what to do with some blues with varied stats ... Abyssal Leather Leggings ... dibs doesn't assign them an EP. Pawn is able to." Loadout.Scorable refused every link with a random suffix, because the suffix's stats are not on the base item id and scoring the id alone answers about a different item. So the tooltip EP line, the upgrade arrow, every "vs. yours" column, the upgrade popup and the per-spec block all said nothing for these drops. That refusal was the narrow fix; this is the complete one that Modules/Declaration.lua's comment already described.

Data/ItemSources.lua gains Items:SuffixStats(link): the client's GetItemStats on the full link minus the same call on that link with field 7 (the suffix) zeroed, so both sides come from one API in one vocabulary. The difference is folded by Items.FoldSchoolStats (an "of Fiery Wrath" school's spell damage counts only for a spec that casts that school, the same gate GetItemScore applies) and scored by the library's ScoreStats. Items:GetEP(id, hand, link) and Items:ScoreForClassSpec(id, classID, spec, { link = }) add it to the base item's score, each on its own weight scale; the base item's set, use and proc value is unchanged. Loadout.Scorable now accepts a suffixed link whose suffix can be read and still refuses one it cannot (an uncached item), failing closed when there is no reader. Loadout:DropDelta and Items:OffHandEPFrom take the link through, and SpecUpgrades.ForItem passes it. Items:SuffixStats memoises a link that answered (never a not-loaded nil), since one hover asks it once per spec.

The WORN side too (self-audit, same day): Loadout:EquippedSlotEP and SpecUpgrades.SlotEPForSpec scored the worn item by id, so a suffixed drop compared against a suffixed piece already worn counted the drop's suffix and not the worn one's, and read as too large an upgrade. Both now pass GetInventoryItemLink, falling back to the base item's score when the worn suffix cannot be read. Specs: Tests/scorable_spec.lua (+2) and Tests/specupgrades_spec.lua (+2), both mutation-checked.

Peer Review on that self-audit (thread 23224f93, reply c9ffb968):

  • Healer credit for "of Sorcery". If the client reports "Damage and Healing Spells" as the single key ITEM_MOD_SPELL_POWER, ItemDB's alias reads it as spell damage only (ItemDB/Scoring/Vanilla.lua:78-80, where the key means aura 13 in ItemDB's own effect data), so a healer got nothing for it. Items.FoldSchoolStats now counts that live key as damage AND healing, which is what the Classic stat does. Which key the client reports is still NOT VERIFIED; if it reports the two halves separately this branch never runs. Release notes must say that healer scoring of random-enchant items is unverified until someone reads GetItemStats on a suffixed link in a Classic Era client.
  • The three field-7 readers are tied together. ML.ItemKey, Declaration.SuffixOf and the zeroing rewrite in Items:SuffixStats each read the suffix field; new Tests/fieldseven_spec.lua runs seven link shapes (plain, positive and negative suffix, blank fields, an enchanted link, a short item string, a bare id) through all three and asserts they agree and that the rewrite changes field 7 only. Mutation-checked by zeroing field 8 instead and by refusing blank fields. The Data file reading a GUI module (ns.MasterlooterUI) is a LOW finding left for a later move of ItemKey into a shared module.
  • The Discord answer overstated that the EP fills in once the item loads; nothing repaints on an item load, so it shows on the next hover. A one-line correction was posted to the player.

NOT CHANGED: the auto Main/Alt tier (Declaration.TierForItem) still withholds its stat axes on a suffixed drop and asks the player. NOT VERIFIED in a client: which stat keys Classic Era's GetItemStats reports for a random enchant's healing and spell damage lines. ItemDB's alias table folds both ITEM_MOD_SPELL_HEALING_DONE spellings, and the per-school damage keys are folded here. Specs: Tests/suffixscore_spec.lua (new, 13, mutation-checked on the GetEP suffix term, the ScoreForClassSpec link term and the zeroed base link), Tests/scorable_spec.lua (+4), and the loot table and roll window column specs retitled to the unreadable-suffix case they still pin.

[v0.7.0] -- resistance sorting, and two group-loot fixes

Changed -- release notes for v0.7.0

Release prep, 2026-10-12. GUI/Intro.lua's UPDATES now lists v0.7.0 only (whatsnew_v070_resist, whatsnew_v070_untick, whatsnew_v070_rollhang, and whatsnew_v070_known rewritten from v0.6.0's with "new" dropped from live rolling); the whatsnew_v060_* keys are deleted from Locale/enUS.lua. KNOWN COST: v0.6.0 shipped only days ago, but Tests/locale_spec.lua pins UPDATES to one New / Changed / Fixed run matching the CurseForge page's newest block, so its bullets cannot be carried; the page still has them. That spec's PAGE_ONLY is now empty, since v0.6.0's LibDBIcon bullet is no longer in the newest block. The CurseForge page gains the v0.7.0 block and drops v0.4.7 (five versions kept), and its feature list and the README now mention the Resistances sort and that unticking "Let Dibs handle loot" opts out of the raid-wide switch. docs/24-test-run-checklist.md names a v0.7.0 key as its example. ItemDB MINOR 43 is ItemDB v1.4.1, which is prepared but NOT yet tagged (its newest tag is ItemDB-v1.4.0, read 2026-10-12), so the player notes say "ItemDB v1.4.1 or newer" and ItemDB should ship first.

Peer Review on the prep's self-audit (thread 2616e94e): the untick bullet first said "works again", which claimed a regression that never happened (the raid switch never honoured the untick), and named only officers, although TurnOnForRaid is gated on Comms:CanPush; reworded. The README and CurseForge opt-out sentence now carry the master-looter exception (Settings.MasterLootRaider). GUI/Intro.lua's CARRY-FORWARD comment is rewritten to say the popup shows only the newest version, which is what Tests/locale_spec.lua enforces.

Fixed -- no second window for a roll already answered, after a loading screen

Peer Review, thread 2616e94e, finding 5. Whether GetActiveLootRollIDs still lists a roll the player answered is undocumented, and Blizzard's own PLAYER_ENTERING_WORLD loop re-opens every id it returns with no answered check (wow-ui-source-classic_era Blizzard_UIParent/Vanilla/UIParent.lua:569). So GUI/LootRoll.lua now remembers each game roll this client answers (rememberAnswered, called beside the one RollOnLoot) in ns.db.answeredRolls for 300 s, and LootRollUI:Resume skips a remembered id. A fresh START_LOOT_ROLL on a reused id forgets it (Show without redraw). Correction to the entry above and to ResetState's comment: the risk was never a second AUTOMATIC answer, because auto-roll lives only in ShowMLRoll (the master-loot prompt), not in Show. It was a window put back up for a roll already answered. Blizzard's own frame for such a roll is left to the client. Specs: Tests/lootrollui_spec.lua (+2), mutation-checked. Player notes: whatsnew_v070_rollhang and the matching CurseForge bullet gain "Dibs does not reopen a roll you had already answered" (Dibs' window only; the game's own frame is the client's).

New -- the Planner's Sort menu has a Resistances group

Asked for 2026-10-09 (resist sets) and recorded unfinished until now. The Build tab's Sort menu gains a Resistances submenu with Fire, Nature, Frost, Shadow and Arcane; picking one orders the gear list by that school, highest first. ns.Items.RESIST_KEYS (Data/ItemSources.lua) names the five keys and StatCategory files them under a new resist bucket instead of Defense. No weight scale prices a magic resist (ItemDB Scoring/Vanilla.lua lists armor only), so GUI/Planner.lua's new sortableStats() appends them to GetWeightStats(), and the menu and the label cache now read that ONE list so a sortable key always has a caption. Captions come from the client's RESISTANCEn_NAME globals with an L["filter_resist_<key>"] fallback. Specs: Tests/source_spec.lua (fire resist now files under resist), Tests/plannerdraw_spec.lua (+1, Sort -> Resistances -> Frost re-orders the list; mutation-checked by removing the group). The sort needs ItemDB MINOR 43 or later, which fills the magic-resist values the Classic clients' GetItemStats never reported (Vanilla 1,371 items, TBC 667; Glacial Cloak 22658 now carries 24 frost). On an older ItemDB the menu still works but finds few items. Contract f93b32b3. No version gate in Dibs: the API did not change, only the data.

Fixed -- unticking "Let Dibs handle loot" did nothing under a raid-wide switch

crimsonmane, #dibs 2026-10-11: "once enabled, disabling it does nothing. still has the dibs windows on grouploot". Settings.LootEnabled ORs in the raid-wide switch (ns.db.raidLootOn), and nothing on the raider's client could end it, so the box wrote the player's setting while every loot window stayed on until the group broke up. Unticking now calls the new Settings.DeclineRaidLootOn (GUI/Settings.lua's General tab callback): the switch is dropped here and, when somebody else pressed it, remembered as ns.db.raidLootDeclined so AdoptRaidLootOn refuses the next re-push. The decline is per group, not per officer (Peer Review, thread fff716e1, finding 3): keyed to one officer, a second officer pressing the button switched the player straight back on. That holds for the officer who pressed the button and then unticked their own box, too. It lapses when the player is no longer grouped (OnRosterSettled), and ticking the box again withdraws it (Settings.WithdrawRaidLootDecline, so the saved field has one owning module). Master-loot raiders are unchanged: under a master looter their loot side stays on regardless, as agreed 2026-10-08. KNOWN COST: the lapse is seen by the 5 s roster debounce, so leaving and joining another group inside that window carries the decline across, the same window the raid switch itself has. Specs: Tests/raidlooton_spec.lua (+7, including a different officer's push, and the presser's own decline), Tests/dkpsettingsui_spec.lua (+1, untick declines, re-tick clears the decline).

Fixed -- a pending roll hung the group after a loading screen

crimsonmane, #dibs 2026-10-11: "caused rolling to hang for the group. after disabling the addon and /reload then the timer showed for the item that was hung up". Blizzard re-opens every pending roll on PLAYER_ENTERING_WORLD (wow-ui-source-classic_era Blizzard_UIParent/Vanilla/UIParent.lua:573-577, GetActiveLootRollIDs -> GroupLootFrame_OpenNewFrame). In replace mode our OnShow hook hid that frame because replaced[rollID] was still set, then LootRollUI:ResetState dropped our own frame: a live roll with no window, so nobody could answer it and the group waited out the server timer. ResetState's comment claimed no roll survives a loading screen; that was wrong and is corrected. The new LootRollUI:Resume runs after it and draws Dibs' window for every roll with time left. When Dibs no longer takes a roll (loot handling off, augment mode, an unreadable item) the game's own frame is shown again. That includes WoW Forever's gamepad roll list, which refills from the same call and could be left hidden if its handler ran first (Peer Review, fff716e1, finding 6). Hiding and re-showing the game's frames is now one helper, setBlizzardRollShown; hideBlizzardRoll wraps it (finding 7). Resume calls Show(id, left, true), and a redraw skips UpgradeAlert:OnDrop, because UpgradeAlert wipes its dedupe memory on the same PLAYER_ENTERING_WORLD and would otherwise re-pop the upgrade alert for every pending roll (Peer Review, thread 90fe4ab6, item 3). KNOWN COST: a drop first seen during the login itself gets no upgrade popup. NOT VERIFIED: whether GetActiveLootRollIDs still lists a roll the player already answered; if it does, auto-roll could answer it twice. That needs an in-game check. Specs: Tests/lootrollui_spec.lua (+5). Mutation checks: the session that started this work reported five production lines broken one at a time, each turning its named test red (not re-run since). Later checks, each run and observed: the gamepad re-show, the redraw's UpgradeAlert skip, and the presser's own decline.

[v0.6.0] -- the Buffs tab rebuilt, live rolling, and raid status

Opened as v0.5.3; renumbered to v0.6.0 at release prep (2026-10-09) because live rolling, the per-item roll tabs and raid status are new player-facing features, which makes this a minor release rather than a patch.

Changed -- release notes lead with the Buffs tab

The operator, 2026-10-10: "i like where we are with the buffs revamp, that should be the highlight for this release." The title names it, and whatsnew_v060_buffs moved from Changed to the top of New in both GUI/Intro.lua's UPDATES and the CurseForge Recent Updates. Its text now also says the list is searchable by stat and that, on Classic Era, ItemDB v1.4.0 adds many more foods (DIBSREQ-IDB-010, MINOR 42: Vanilla foods 11 -> 67; TBC already had its foods, so no TBC claim). Stones stay in their own Fixed bullet only, with its wrong-weapon caveat. Peer Review (thread 7b2948d7) found both of those (a Classic-only claim stated for every flavour, and stones announced twice) and a Planner comment that still gave the buff cap as 16; it is 32 (Buffs:Cap). It also found nothing read the page: Tests/locale_spec.lua now asserts UPDATES and the page's newest Recent Updates block have the same bullet count per group, with the page-only LibDBIcon bullet declared by name (+1 test, mutation-checked: moving the Buffs bullet back to Changed in the popup alone turns it red).

Fixed -- release-prep reviews (Peer Review, d25eeeb0 and 9937c49d)

What's New lists v0.6.0 only (+whatsnew_v060_resize, with its LibAceGUIWidgets caveat, also on the CF page); every retired whatsnew_* key deleted. Live MS under a Main-only gate says roll_no_raid_status_main (+2 specs, mutation-checked). Two strings name the "Loot & Rolls" tab; the CF page names no files. The stone bullet says it needs ItemDB v1.4.0; a stone that does not fit says so in its tooltip (buffs_tip_nofit, +1 spec, mutation-checked). Boards retired (D6): docs/AUDIT.md and Tests/HARNESS_CONTRACT.md imported into the writ inbox and deleted, with Tests/auditdoc_spec.lua, which only guarded the board; CLAUDE.md points at the inbox. Test checklist step 9 made version-neutral.

Fixed -- the whole suite could not finish: nocaller_spec scanned the corpus once per export

Found by running the suite in chunks: Tests/nocaller_spec.lua alone ran past five minutes, and with a filter that matched no test it finished, so the cost was in the sweeps. references and specReferences ran a gmatch over the whole corpus for every export. wordCounts now counts every word in one pass, reproducing the gmatch's skip of an occurrence that shares a separator with the previous one, so the counts are meant to be identical; every count-based assertion and ceiling passes unchanged (old and new were not run side by side). Whole suite at release prep (2026-10-10): 6016 passed, 0 failed. No player-visible change.

Changed -- under a master looter, every raider's loot side is on

crimsonmane, accepted by the operator 2026-10-08: "Let Dibs handle loot" governs group loot only. Settings.LootEnabled ORs in Settings.MasterLootRaider() (master loot, group state ready, not the ML). The reminder popup is dropped. Tests/raidlooton_spec.lua +5, mutation-checked. Peer Review (03a3c612): "not the ML" asked AmMasterLooter (window-only), so between corpses an opted-out ML authored loot-log/SR+ entries; now RaidMasterLooterIsMe (+3).

Changed -- the Buffs tab is laid out like the Build tab

The operator: "i'm not really happy with the layout of this page at all". PlannerUI:DrawBuffs now draws every PLANNED buff as a lit toggle chip at the top (pooled per buff key; a click removes it), a slot column with one row per category (raid, world, totems, then each consumable slot) and its picks, and the library's W:CreateSearchBox over a scrolling list of the selected category's options, best EP first, up to 150. The "+ Add" dropdowns and their plain menu search box are gone. Tests/buffsdraw_spec.lua rewritten (7, mutation-checked). Slow open/close fixed: EP and base stats are cached per repaint, and slot/list rows are pooled -- each repaint used to detach a container of rows that stayed anchored to the scroll content, so closing re-laid out all of them (+3 tests). LibItemDB MINOR 42 adopted: stones score via Items:WeaponDamageEP at the PLANNED weapon's base MELEE_WEAPON_SPEED (live speed only as fallback), and Buffs:EntryFits (GetImbueWeapons) parks a stone that does not fit its weapon, e.g. a weightstone on a sword (DIBSREQ-IDB-011, +7 tests, mutation-checked); the food strip is old-library only (weapondamage_spec).

Changed -- every on/off switch in Dibs' windows is a toggle chip

The operator, 2026-10-08, with Grouper's type chips as the reference: "they added toggle tokens, and they look REALLY nice. we should use them where it makes sense", then "can we also clean up dibs settings to use the toggle tokens? i think it will allow us to make the settings much more clean", then "update the windows to use toggle tokens if it makes sense". New C.Toggle (Constants.lua) builds LibAceGUIWidgets' LAGW-Chip (MINOR 40) on a plain frame, or the old checkbox-with-label on a library without chips, and returns one frame with GetChecked / SetChecked / Click / SetLabel / SetOnChange / SetEnabled either way.

  • View filters: the Planner's Upgrades only and Hide 2H, the Raids tab's Group alts, Show ex-members, Show test runs and the Accounts pane's In raid only, the DKP tab's Show ex-members and Group alts, the Loot Log's Show test runs. The Raids tab's list insets moved from -26 to -28 for the 22-tall chip.
  • Settings: B:Check (GUI/Settings.lua) builds a chip, and CONSECUTIVE ones share a line, wrapping at the tab's width; any other row (a number, a dropdown, a header, a button) ends the run, so the next switch starts a new line. The minimap switch goes through B:Check too, with its own get/set pair. Each chip's _dibsID is the setting key it writes.
  • The other windows: the loot table's "Decide main/alt for me", My Main / Alt's "this is my main" (moved up to -239 to clear the dropdown below; Tests/declbuttonoverlap_spec.lua now counts it as a button and names a chip in its failure message), the master looter's priority picker (a pooled chip per name), and the Soft Reserve settings tab's two switches. Only the Buffs picker stays a checkbox: its row is an item (icon, EP, tooltip, shift-click to link), and a chip would take the row's mouse.
  • Peer Review (thread e384723d): Settings lines are packed for the window's minimum width (MainWindow.SETTINGS_MIN_W), since a tab is drawn once and narrowing it used to push chips past the edge; a spec pins that a chip has a width when made and SetLabel resizes it; and one pins that the enchanters' disposal roll never lands on the item's roll tab. All three mutation-checked.
  • Specs find toggles by the _dibsToggle marker or their _dibsID, and set one by clicking only when it is not already in the wanted state (a chip's click flips it, so "SetChecked then Click" flipped it back). Tests/constants_spec.lua drives both paths of C.Toggle; Tests/settingsui_spec.lua pins the line sharing, mutation-checked (one toggle per line turns it red). Not seen in a game client.

New -- live rolling: the Gargul-style loot window, as an officer option

crimsonmane (#dibs 2026-10-01); the operator: "i'll leave that as is as an option, then build this new". Officer setting lootTableStyle ("Loot window", Loot & Rolls): table (default, unchanged) or live. The master looter stamps it into the table (t.live, TablePayload.live); both faces open it locked and executing (openLive), so there is no picking phase. The rows lose Main / Off / Pass; a bar above the roll tabs reads Rolling on [item], then Winner! name, with MS / OS / Pass that throw a real /roll (Pass is /roll 1) once per row (UI:LiveRoll), greyed with the reason when nothing is called, already rolled, not listed, or barred by raid status. OnRollNow rolls nothing for anyone on a live table. The roll tab is sorted MS, OS, Pass, highest first, with everyone eligible who has not rolled as Yet (UI.LiveList). The master looter counts a 1-1 roll as a pass (ML.LivePass), never as a roll. A row's item tooltip names its competitors (UI.RowTooltipText, both styles; BindFrame's extra now follows the BiS block). Late rows are closed at once. EPGP and auction tables keep their own flow. The master looter awards (crimsonmane, 2026-10-08: "masterlooter should pick the name and press Award, because someone might roll on something they shouldnt"): a live row is called with manualAward, so LootSession.Resolve turns the rolls into a ranked award list (tier, then roll; no tie-break) and the phase holds at "award". The list lands on the live bar (state.liveAward, top roll picked); a click on a roll-tab line picks someone else (UI:PickLiveAward); Award goes through ML:AwardTo -> ResolveRoll, as EPGP's does. The two-part table still awards on the die. Raiders learn the rolls closed from loot_table_award (ML-gated, also in the reload catch-up): UI:OnAward greys the bar and UI:LiveRoll refuses until the result (+4 tests). /dibs abandon then a re-call of the same row left raiders latched (myRolled), the old rolls on the tab, and the award open: RollPayload now carries a call token (sender:time:counter) and a new one resets all three, both table styles (+3 tests). A handover clears the old call and award (AdoptMasterLooter). loottablegate_spec sweeps every groupOnly type. Raid assist is not built; Take all is the Distribute Later setting (docs/20). Eight tests in loottableui_spec and four in livepass_spec (live and two-part), all mutation-checked. Peer Review (thread ea5329b9): MS / OS / Pass only asked the call's only list, so on a soft-reserved or Competition row under Standard a non-competitor could throw a /roll the master looter then refused. Both the painter and UI:LiveRoll now ask liveListed, which adds LootTable.Eligible. Also pinned: a setting change mid-table leaves the open table's style alone. loottableui_spec now loads ItemInteract, as the TOC does, so the row hover is driven for real. All mutation-checked. Not seen in a client.

Changed -- LibAceGUIWidgets MINOR 41 adopted: windows re-lay-out during a drag on their own

ClearFrame now calls DoLayout from its own onResize (DIBSREQ-LAGW-003, which Dibs raised), skipping a second layout when AceGUI's content hook already ran for that size. Dibs' staged copy, C.LiveRelayout, is deleted with its two calls in GUI/MainWindow.lua and its five tests: on MINOR 41 it would have replaced the library's onResize and lost that guard. A player still on MINOR 40 gets contents that settle at drag-end, as before the helper existed. Not dragged in a client.

New -- the loot table shows every roll, one tab per item

The operator, 2026-09-30, answering Crimsonmane's "people did not like not knowing (not seeing) what everyone else rolled": "add a tab to the current rolls window that shows rolls per item ... it would auto generate 3 tabs, 1 for each piece of gear. and as the rolls come in, populate them into the correct tab", with "< and > arrows at either side of the tabs", and "this should be in the widgets library".

  • The strip is LibAceGUIWidgets' LAGW-TabStrip (MINOR 40, delivered on contracts thread cb965300), detected by AceGUI:GetWidgetVersion("LAGW-TabStrip"). With an older library the window is unchanged.
  • The panel (GUI/LootTable.lua, paintRolls) takes the EPGP board's slot under the row list -- the two never meet, since an EPGP table throws no dice -- and appears once the master looter calls a row. One tab per row in table order; the shown tab follows the row being rolled until the player clicks another, and the next row the master looter calls takes it back. Each line is the roller, the number, and Main / Off spec / Tie-break or the range thrown (UI.RollLine); the winner's line turns green once the row ends. Not on auction or EPGP tables.
  • The wire: a new loot_table_rolls broadcast (LootTableSync.RollsPayload, master-looter gated like the other table broadcasts). The chat handler in GUI/Masterlooter.lua now hands onRollSeen the roll the tally COUNTED (nil on a refusal), so only counted rolls appear -- a refused roll is already reported to the master looter and did not count. Live rolls go one per message; a reloading raider's catch-up (UI:OnTableAsk) sends each rolled row's whole list with full, which replaces rather than appends. The master looter ignores its own echo. All of it is cleared with the table (OnTable, StartTable, ResetState), so a new corpse starts with no old tabs.
  • Fixture defect found on the way, fixed: Tests/loottableui_spec.lua's "the window never appears on its own" block gave its second namespace this file's own ns.Comms, so its copy of LootTableSync re-registered every message type over the file's real ones; any broadcast asserted in a later block was provided from a window with no table and pushed nothing. Its RegisterSyncType is now a no-op.
  • Eleven tests in Tests/loottableui_spec.lua ("the rolls per item"), mutation-checked: removing the broadcast, the full replace or the master looter's echo guard each turns one red. Not seen in a game client.

Fixed -- the Planner's Reset button left every gear-list filter in place

The operator, 2026-10-05, from WoW Forever: "bug: the reset button on forever isn't clearing out the filters". The loadout bar's Reset (GUI/Planner.lua, resetBtn) only called Loadout:ResetToEquipped, so Upgrades only, the search box, the source dropdown, the type dropdown and the sort stayed as they were. The filters live in locals of the Build-tab draw and Reset never touched them. Nothing here is flavour-gated, so Classic Era and TBC had the same bug. New PlannerUI.ResetFilters clears all six, Hide 2H included, through the same setters the dropdowns and ApplyView use, so the saved copy in DibsDB.filters.planner is cleared too and a /reload does not bring them back. Reset now calls it after the loadout reset; loadout_reset_tip says so. Pinned in Tests/plannerdraw_spec.lua.

Fixed -- CurseForge was told LibDBIcon is embedded, so the app never installed it for Era or TBC

The vendored Libs/LibDBIcon-1.0/LibDBIcon-1.0.lua arrived with --@curseforge-project-slug: libdbicon-1-0@ on line 1. The BigWigs packager reads that marker from every packaged .lua and records the slug as an embedded library, overwriting the required-dependencies entry .pkgmeta set (release.sh:1078, then :1880-1890). Dibs.toc and Dibs_TBC.toc hard-depend on LibDBIcon and do not load the copy (only Dibs_Camelot.toc does), so a CurseForge-app install on Era or TBC got no LibDBIcon. Silent: the packager exits 0. Marker deleted; Tests/pkgmetaclaims_spec.lua now sweeps every vendored file the Forever TOC loads for it, with a floor, mutation-checked (red with the marker restored). Reported on inbox threads ec639087 (TOGBankClassic) and c7938f3e (Peer Review).

New -- raid status: which guild ranks may roll in a raid

The operator, 2026-10-05: "it should decide whethere they can roll in a specific raid. like if you set you 'progression' raid as AQ40 for example, you have to have AQ40 status to roll on an item, or to roll main on an item. so we need to be able to set it and have it enforced", and earlier the same day "this needs to be an officer setting and synced, so the auto roller accounts for it."

  • Model: new Modules/RaidStatus.lua. A raid may carry one rule: the guild rank indices (0 = GM, stored by index so a renamed rank cannot split clients) that hold its status, and a mode -- "roll" (no status, no roll at all) or "main" (no status, no Main Spec roll; "ms" and soft reserve's "main" are both the top tier, a tie-break "tb" is not). A rule with no ranks ticked is not enforced, the same call LootSession.IsEligible makes for Manual with nobody flagged. A player the roster does not know (a pug, or the roster not loaded yet) holds no status. Loaded after Modules/RaidSystems.lua in all three TOCs.
  • Sync: a raidStatus field in Settings.SerializePush / ApplyPush, under the same three rules as the other per-raid officer maps (keyed by raid, absent-means-silent, replace-not-merge); every edit calls Settings.Touch, so the newest stamp wins.
  • Enforced by the master looter: LootSession.New takes an injected statusGate; AddRoll and AddChoice refuse with the new reason no_raid_status (refused, never quietly re-filed as off-spec). LootTable.New takes the same gate and LootTable.Select refuses a barred pick; a pass is never barred. GUI/Masterlooter.lua builds the gate from RaidStatus.Gate(raidKey) in both startRoll and RunTableRow, and the CHAT_MSG_SYSTEM handler always prints ml_roll_no_status (debug on or off), because the likeliest roller is a raider without Dibs. A refused choice reaches the raider as lootack_reason_no_raid_status.
  • Enforced on the raider's own windows: GUI/LootRoll.lua's ML prompt and GUI/LootTable.lua's rows grey the barred buttons with the reason (greyed, never hidden; Pass untouched). The auto-roller moves a decided Main to Off-spec when only Main is barred, rolls nothing when rolling is barred, and UI:OnRollNow will not throw a pick made before a rule arrived. Both table builders (the master looter's and the raider's) carry the gate, so a raider who takes over as master looter keeps enforcing it.
  • Officer UI: a Raid status block on the Loot settings tab, one dropdown per raid: the three modes, then a tick per guild rank (GuildControlGetRankName, which is 1-based and which the Classic Era calendar's mass-invite menu reads for any member; LibGuildRoster's rank names are the fallback). Greyed for anyone who is not an officer, and a click from a non-officer is ignored. New general row builder B:Menu for a dropdown whose menu the caller builds. New Comms:GuildRankOf(name).
  • Peer Review (thread 331a2c79), all three taken: (R1) a rank index is a position, so a rank the GM inserts above a ticked one silently moves the status; each ticked rank's name is now recorded beside it, RaidStatus.MovedRanks reports any that no longer match, the settings row says "(a rank changed -- check)" and the master looter gets one chat line per raid per session (ml_raidstatus_moved). It is a warning only: enforcement stays on the index so two clients still agree. (R2) the single-item Roll's gate is now pinned through ML.RollSlot in lootmethods_spec. (R3) the master looter's raid key now rides the roll (roll.raid) and the table (TablePayload.raid, t.raid), and every raider window gates on that key, falling back to its own CurrentRaid only for a master looter on an older build -- so the two cannot disagree about which raid it is.
  • Tests: new Tests/raidstatus_spec.lua (27), plus raid-status blocks in lootrollui_spec (6), loottableui_spec (6), settingsui_spec (6), strayroll_spec (2) and lootmethods_spec (2). Every production line they name was mutation-checked: broken, seen red, restored. Nothing here has run in a client.
  • constants_spec's C.HoverWhileDisabled site count for GUI/Settings.lua goes 5 -> 6 for B:Menu.
  • DKP / GDKP bids, when the officers turn it on (2026-10-08). The operator: "only if the officers turn it on. these need to be settings so each guild can chose". New officer setting raidStatusBids ("Status also needed to bid", Raid status block), off by default and in the push. On, DKPAuction.Bid refuses a bidder without status with no_raid_status (checked after pug eligibility, before the bid's own rules), and the bid popup greys Bid and says why (DKPBidUI.PaintStatusGate); Pass stays live. A bid is judged as a Main Spec claim, so mode "main" bars it as "roll" does. EPGP needed nothing: its award list is the table's picks, which the gate already checks. Five tests in raidstatus_spec, three mutations each seen red.

Changed -- .pkgmeta carries no comments

The operator ruled out comments in .pkgmeta entirely on 2026-10-03, and ClassicThreatLogger's review found Dibs' still had two comment blocks. Both are removed. What they explained (no trailing # on a list item, bare folder names, single-star quoted globs, every hard dependency listed explicitly) now lives in CLAUDE.md's Testing section. Tests/pkgmetaclaims_spec.lua gains "carries no comment line at all" so a comment cannot come back unnoticed. No player-visible change.

Changed -- the library-contracts board is retired

docs/LIBRARY_CONTRACTS.md was imported into writ's inbox and deleted, and docs/README.md and the C.LiveRelayout comment in Constants.lua no longer point at it. The one request still open, ClearFrame re-laying out its contents during a resize drag, went to LibAceGUIWidgets as a contract; MINOR 41 shipped it (above).

[v0.5.2] -- raid sync fixes from the first Zul'Gurub night

Opened 2026-09-29 for crimsonmane's raid report, on the operator's instruction: "a few things for dibs v0.5.2".

Fixed -- P2P sync starved in a real raid: wishlists, the live raid and every ledger

Reported from a Classic Era Zul'Gurub raid: "wishlist are not syncing, none of us can see anyone's wishlists", "all players show No Raid In Progress under Raids>Live except me", and "pretty much looks like nothing is syncing at all, everyone is independent except the loot window". The loot window rides the direct DibsPush channel; everything else rides DeltaSync's P2P hash-offer exchange, and that is where the fault was.

DeltaSync's P2P contract (P2PSession.lua:48-55) requires the host to call p2p:ReleaseSendSlot(requester) after serving a request and p2p:OnItemCompleted(itemKey, sender) after making one. I never called either, anywhere in Dibs. A provider has three outbound slots in total and a requester three sessions, so each was pinned for its full safety timeout (90s and 180s). Two clients testing each other fit inside that. A raid forming does not: twenty raiders ask the master looter for the raid record in one roster-settle window, three are served, the rest are answered sync-busy, retry five times at 20s and give up.

  • Modules/Comms.lua onDataRequest now releases the send slot on every exit, through a new local releaseSendSlot. A served answer releases it when the reply has DRAINED, through SendData's onComplete (DeltaSync MINOR 18+); exits that send nothing (nothing to send, sender refused, unknown type) release at once. Peer Review finding A (thread 76b9da8b): my first cut released on SendData's return, which under AceCommQueue is up to ~70s before the last chunk leaves, and so turned the three-slot cap into no cap. A DeltaSync older than 18 has no callback and keeps release-on-return.
  • The onSyncAccepted callback records each sent QUERY in awaiting, and completeArrived completes it when that peer's answer of the owning type arrives. Peer Review finding B: my first cut completed it as soon as the query left, which gave up DeltaSync's delivery timeout -- the thing that fails a session whose provider logged off mid-reply and schedules a catch-up. The offline-peer and no-handler exits fail the session at once, so DeltaSync's catch-up retries. KNOWN COST: a provider with nothing to send holds that session until the delivery timeout, the same wait as before.
  • Completion is per ITEM and per SESSION (Peer Review's second pass on the same thread). It had matched by type, and seven types own more than one key (LedgerSync, Characters, WishlistSync and SoftResSync's four), so the first answer completed every awaited key of its type. Each now carries an Answers(itemKey, data) handler naming the key an answer belongs to. And each awaited entry records DeltaSync's session id at query time: if that session has since been failed and replaced, a late answer drops the entry instead of completing its successor. Tests/syncanswers_spec.lua, mutation-checked.
  • No wire change: a v0.5.1 peer is unaffected, and a mixed raid benefits on whichever side is updated.
  • Tests/commscatchup_spec.lua pins every exit and both findings, mutation-checked.

Not verified in a client. This is the one cause I found and fixed by reading the code; whether it is the whole of Crim's report needs a raid on v0.5.2.

New -- officers can turn Dibs on for the whole raid

Crim's ZG follow-up: a raider with a wishlist missed a roll because "Let Dibs handle loot" was off. The operator's call: officers turn Dibs on for everyone and sync the raid settings, without touching anyone's SR or wishlist. Settings.TurnOnForRaid (Settings, General tab) stores ns.db.raidLootOn and pushes settings carrying raidLootOn; LootEnabled ORs it in and never writes lootEnabled. It lapses when ungrouped, is taken only from a sender InSameGroup (the push also goes to GUILD), and is adopted before the recency guard so a late joiner holding the same stamp still gets it. The client that pressed it re-pushes group-only (Comms:Push(key, groupOnly)) when the roster grows. Tests/raidlooton_spec.lua, mutation-checked.

It also forces autoRollTier and autoRoll on while it holds (Settings.RAID_FORCED, read in Settings.Get, never written). The operator: the switch fixes a missed roll "only if pushing autoroll is part of this", and "if you have auto rolling on, it'll just fix it" for a raider who never noticed the roll start. Known cost: those two boxes read ticked during the raid and unticking them does nothing.

Peer Review (thread 7b592d3d): only the client that pressed the button carries raidLootOn on a push (R1 -- an adopted copy let any assistant re-spread it, ageless, past the recency guard), and a received switch lapses when its author is no longer InSameGroup (R2 -- a raid converted to a party kept auto-roll forced). Both mutation-checked. Its second pass on forced auto-roll found the mechanics sound (it fires only when the Main/Alt setup singled out one button) but asked that raiders be told Dibs now rolls in their name: raidloot_on_chat, settings_raidloot_active, the What's New, CurseForge and README text all say so, and that the roll is FINAL: LootSession.AddPass refuses a player who already answered (already_answered) and the prompt hides Pass once a roll is sent (GUI/LootRoll.lua, the sent branch). Not reviewed, not verified: the loot table's fold on the master looter's face; nothing in this release has run in a client.

New -- the loot table folds down, and closing it says how to get it back

Crim: "the inability to see where I'm going, but also I forgot the /command to bring back the ML window"; the operator: "it's too big, can we compact it somehow?" A collapse button beside Close folds the window to its title, clock and status line (UI:SetCollapsed, saved in lootTableCollapsed). UI:Refresh now wraps the old body (UI:RefreshContent) and applies the fold after it, so the repaint's own show/hide rules cannot undo it. Closing a live table was always only a hide; it now prints once per table that /dibs ml (a new alias of /dibs loot) brings it back. Tests/loottablecollapse_spec.lua, mutation-checked; the master looter's face folded and a real client's resize floor under a 58px height are not verified.

Fixed -- Zul'Gurub coins opened the loot table on every trash pack

Same report: "zulian coins triggered loot window, was obnoxious". ZG runs master loot at green and the coins are green, so scanLootItems (GUI/Masterlooter.lua) gave each one a row. New pure ML.IsTokenDrop(classID, quality): a quest-class item (Enum.ItemClass.Questitem = 12, ItemConstantsDocumentation.lua) below rare. An unclaimed token is scanned at quality 0, so the officer's matsAction decides it the way it decides cloth.

The line is quality because ItemDB's own data draws it there (Data/Vanilla/_core/Quest.lua): the nine coins and the AQ20 Stone Scarab are green, the Hakkari bijous blue, the Primal Hakkari idols and the Head of Onyxia epic. A class-only rule would have taken rows from the last three. A reserved token (or a wishlisted one under Wishlist Loot) keeps its row. Four tests in Tests/lootmethods_spec.lua.

Fixed -- WoW Forever: Dibs' tooltip lines repeated a dozen times

Reported on Forever 2026-09-29 (Adestar, screenshot): the EP, upgrade and per-spec lines stacked down the tooltip, interleaved with two other addons' lines. On the data-driven tooltip (TooltipDataHandler.lua, Forever tree) InternalProcessInfo runs every AddTooltipPostCall for each info it processes -- the primary one and every info another addon APPENDED (info.append, which skips ClearLines) -- and RebuildFromTooltipInfo replays the whole list on each refresh. My post-call never asked which pass it was on. New UpgradeTooltip.ShouldAugment reads GetProcessingTooltipInfo() and skips an appended pass. Same fix reaches the TBC build, which takes the same path; Classic Era uses OnTooltipSetItem and is unchanged. Two tests in Tests/upgradetooltip_spec.lua, mutation-checked. Not verified on Forever: which addon appends was not identified (the "Not suitable" line is in no addon installed here), and the other addons' repeated lines are theirs to fix.

Changed -- the call to roll is a raid warning

Same report: "roll notification not done as a raid warning. would help those not using dibs and/or those not auto-rolling". New C.RollCallChannel() (Constants.lua) answers RAID_WARNING in a raid when the player is leader or assistant (UnitIsGroupLeader / UnitIsGroupAssistant, the pair Blizzard's own raid UI uses), and otherwise falls back to C.AnnounceChannel(), since a raid warning from anyone else is dropped by the game. Only the two roll-call lines use it (the single-item roll and the loot-table row roll in GUI/Masterlooter.lua); awards, ties and whispers stay in raid chat so the warning keeps meaning "roll now". Four tests in Tests/constants_spec.lua pin the rule; the two call sites are not driven under a raid leader by any spec.

Also: settings_lootenabled_tip tells raiders they never need to switch Dibs off for trash, and no longer says "On by default" -- the default has been off since 2026-08-15.

Also: a README / CurseForge feature-parity pass (2026-09-30) brought each document up to the other's features, and settled eight places they contradicted each other against the code: attendance does not need the loot switch, low-quality loot has four options (Settings.CHOICES.matsAction), the roll window Shift-clicks to link and Alt-clicks to compare (GUI/LootRoll.lua), alts stay per-character with an opt-in Group alts, pug authority includes assistants, and neither document now claims TBC is verified live. The CurseForge Known issues name EPGP awards too.

Also: the release doc-pass (2026-09-30) put the raid-wide switch and the loot table's collapse in the What's New list (whatsnew_v052_raidon, whatsnew_v052_collapse), the CurseForge page's v0.5.2 New group, the README and docs/20-loot-table-window.md; docs/05-settings.md carries the switch. The What's New popup carries the v0.5.2 changes, and v0.4.7 and v0.4.6's remaining headings moved to CHANGELOG_ARCHIVE.md to bring this file back under the 120,000-character ceiling.

[v0.5.1] -- WoW Forever fixes, small upgrades show their arrow, and alts plan in their own gear

Opened 2026-09-27, straight after v0.5.0 was tagged (34ec9e6), on the operator's instruction: "we can continue under v0.5.1 after the release". v0.5.0 below is closed.

Fixed -- WoW Forever API sweep (and one shift-click bug on every client)

Operator, 2026-09-28: "we can do the wow forever work now". A read-only sweep of every WoW API Dibs calls against the Forever client source (F:\Blizzard API Docs\wow-ui-source-forever, pulled 2026-09-26) found no load-time error, master loot, group rolls, tooltips and the calendar all present in the shape Dibs reads, and these gaps, each now read both ways with Classic Era and TBC unchanged:

  • Chat lockdown secrets. Forever flags the raid/party/say/yell/whisper and system chat events SecretInChatMessagingLockdown (ChatInfoDocumentation.lua). RaidRoster.NoteGUID compared and pattern-matched the sender GUID and the master looter's /roll tally parsed the system line, both from tainted code. Each now skips a value issecretvalue reports as secret (feature-detected, absent on Era/TBC).
  • Pop-up edit boxes. Forever's StaticPopup is GameDialogMixin: the edit box is .EditBox via GetEditBox() and the buttons sit in ButtonContainer via GetButton1(). Soft Reserve's save-template, save-setup and import did nothing there and export opened empty; Enter did nothing in every Dibs pop-up. New C.PopupEditBox reads both shapes and C.PopupEnterClicksAccept reads both button shapes; Soft Reserve and the Planner's talent-save pop-up use it.
  • Shift-clicking a link into a Dibs box -- broken on Classic Era too, not only Forever. ItemInteract.AcceptLinks hooked the ChatEdit_InsertLink global, but both the Era and Forever client source route every modified click through ChatFrameUtil.InsertLink, and the global is only a deprecation-fallback alias (Blizzard_DeprecatedChatInfo/Deprecated_ChatFrame.lua). Hooking the alias never fires. It now hooks ChatFrameUtil.InsertLink wherever it exists. The 2026-09-25 protected-mats fix was never confirmed in a client, and by this reading it could not have worked on a current Era client either.
  • Profession skill lines. Forever has only C_SkillInfo, whose GetSkillLineInfo returns a table. New Professions.SkillLineAPI adapts it to Classic's tuple; the profession scan and Disenchant.LiveSkill (the enchanter's rank for disenchant offers, which read nil on Forever) both use it. ExpandSkillHeader is feature-detected the same way.
  • Every Forever player was scored as "default" spec. Forever has no GetNumTalentTabs, so Items:PlayerClassSpec took the Mists path -- and Forever's GetSpecialization answers one id per class (1482-1491, Camelot/Blizzard_ClassTalentUtil.lua) that SPEC_BY_ID cannot map. Forever keeps the three classic trees as GROUPS of one trait tree; new specFromTraitGroups reads each group's spent points the way Forever's own talent frame does (C_ClassTalents.GetActiveConfigID, C_Traits.GetConfigInfo, GetGroupDisplayInfoByTreeID, GetGroupCurrencyInfo), picks the tree with the most, and maps it through SPEC_MAP like Era. TRAIT_CONFIG_UPDATED now clears the cached spec. The group order was MEASURED on a Forever paladin (operator's /dump, 2026-09-28): Holy, Protection, Retribution at orderIndex 0, 1, 2, which is SPEC_MAP's order; the other classes are assumed to match. Specs: 4 in Tests/playerspec_spec.lua, mutation-checked.
  • Smaller: Items:ItemSchools prefers C_Item.GetItemStats; the Wishlist event-tag menu anchors through GetMouseFoci where GetMouseFocus is absent.
  • The talent planner starts from your real Forever talents. On Forever the planner already drew LibItemDB's tree, but overlayLiveRanks needs talent tabs and bailed, so it opened empty. New overlayTraitRanks (Modules/Talents.lua) walks the trait tree's nodes, takes each committed activeRank, follows the active entry to its definition's spellID, and matches it to the talent carrying that spell in LibItemDB's tree -- which is also what tells the three Weaponmaster entries apart. A node whose spell the tree does not list is skipped rather than guessed. NOT VERIFIED: that a trait definition's spell is the same rank-1 spell LibItemDB lists.
  • Gamepad roll list. Forever's gamepad-mode GamepadGroupLootRollFrame answers every roll in one list and was not hidden by the replace-the-roll-window option. It is now hidden when every roll it lists is one Dibs replaced, and left up while it holds a roll Dibs is not showing.

Specs: Tests/forevercompat_spec.lua (17 cases) and two new cases in Tests/lootrollui_spec.lua, all mutation-checked; the Masterlooter /roll guard is not specced (its frame is file-local). Only the spec-order reading has been checked in a Forever client (the paladin /dump); nothing else here has run there.

Peer Review (thread 857fcfc2), all five taken. overlayTraitRanks matched on overriddenSpellID and fell back to a choice node's first entry, either of which could put a rank on the WRONG talent; it now uses spellID only and the first entry only on a single-entry node. AcceptLinks also hooks the ChatEdit_InsertLink alias, which still holds the unhooked original. The /roll skip is now ML.ReadRollLine (specced in Tests/mlrollline_spec.lua) and tells the master looter once per roll, through a sayStatus helper shared with the resume line so it lands on whichever window is showing. Not verified: whether a later status repaint during the same roll paints over it. Characters registers TRAIT_CONFIG_UPDATED. Each fix mutation-checked.

Fixed -- the Planner hid white gear while you were still wearing white gear

Operator report, 2026-09-27: a low-level paladin with the Copper Mace recipe searched the Main Hand list for it and got "No gear matches your filters". Copper Mace is a white item, and itemsForSlot (GUI/Planner.lua) listed white items only when the "Show white items in the planner" setting (showWhites, off by default) was on. The empty-list message never mentioned that setting, so there was no way to find it. While levelling most gear on offer is white, so the list read empty in exactly the slots that most needed an answer.

New PlannerUI.MinQuality sets the quality floor: whites are listed when the setting is on, and also whenever the planned character wears a white or grey item in that slot or nothing at all, since a white can then be a real upgrade. It keys on what is worn rather than on character level, so there is no level threshold to pick; once a green is in the slot, whites drop out again. The floor is part of the slot-list cache key, so a gear swap never serves the other floor's cached list. The "no matches" message now names the setting. Specs: Tests/altgear_spec.lua "PlannerUI.MinQuality", mutation-checked (removing the worn-quality rule turned the reported case red); Tests/plannerrowshape_spec.lua's source guard follows the new itemsForSlot signature.

Found at the same time, and not a Dibs defect: on WoW Forever Copper Mace also read "Dibs EP: 0" with no per-spec lines, because ItemDB's Forever weapon rows carry no damage-per-second (the same item scores protection 6.84 and retribution 20.86 on the Vanilla data, measured through Dibs' own scorer). Filed with ItemDB as DIBSREQ-IDB-009. Delivered the same day in ItemDB's working tree for LibItemDB v1.1.3 (Forever data rebuilt from an in-client item scan); through Dibs' own scorer on that data Copper Mace now scores protection 6.81 against the Anvilmar Hammer's 3.79, and retribution 20.77 against 11.57. The same rebuild moves Forever caster scores (hit and crit now land on both melee and spell keys, and many items gained spell power). Not verified in a client.

Dev tooling -- the replication watcher no longer deletes a live file from the replica installs

Peer Review from TOGProfessionMaster (thread e2a9ef3a): Sync-File in wow-version-replication.ps1 honoured a Deleted event without checking that the source file was gone. An editor that saves by replacing a file raises Deleted and Changed for one save, in either order, and when Deleted ran last the file vanished from every replica while the source still had it; TOGPM lost Compat.lua from three installs that way. Dibs' copy had the same branch. It now treats a Deleted event for a file that still exists as a change. Checked afterwards: _classic_, _anniversary_ and _classic_beta_ hold the same 104 addon files as the source, so nothing had been lost here. The running watcher keeps the old code until it is restarted. Not packaged (the script is dev-only).

Fixed -- WoW Forever: the bag arrow never showed in the stock bags

Operator report, 2026-09-27: the tooltip arrow worked on Forever and the bag-icon arrow did not. BagArrowUI.AttachBlizzard hooked ContainerFrame_Update, and when that global was missing it returned "no ContainerFrame_Update" and installed nothing. Forever has no such function. Its bags are the retail-family mixin frames (camelot loads the mainline container files), which redraw through each frame's own UpdateItems, list their buttons through EnumerateValidItems, and put several bags in one Combined Backpack frame, so the bag has to be read from each button's GetBagID. The bags-greenarrow UpgradeIcon texture is on those buttons and nothing in that client drives it, as on Era.

AttachBlizzard now falls back, only where ContainerFrame_Update is absent, to the new BagArrowUI.AttachFrames. That post-hooks UpdateItems on every frame in ContainerFrameContainer.ContainerFrames plus ContainerFrameCombinedBags, per frame rather than on the mixin, because Mixin has already copied the method into each frame. PaintFrame gained the per-button branch, sharing a new paintButton with the Era path. Era and TBC still take ContainerFrame_Update, unchanged. Three new tests in Tests/bagarrowui_spec.lua, mutation-checked (each branch removed turned its test red). Verified by the operator in the Forever client after a reload: the arrow shows in the Combined Backpack. The bank window was not checked.

Fixed -- small upgrades rounded to zero, so the green arrows never fired at low level

Operator report, 2026-09-27: a paladin in 5-armor Squire's Boots hovering 16-armor Ragged Leather Boots saw "Dibs EP: 0", three "+0 EP" spec lines and no arrow. The scoring was right; the rounding threw it away. ItemDB weighs armor at 0.02 EP a point for a Protection paladin and 0.005 for Holy and Retribution, so the swap is worth about +0.22 or +0.06 EP. I rounded every EP figure to a whole number (Loadout:DropDelta, Loadout.ItemEPFor, SpecUpgrades.TextFor), and the tooltip arrow and bag arrow both test delta > 0, so a sub-0.5 upgrade could never show one. At low level nearly every upgrade is under 1 EP.

New C.RoundEP (Constants.lua) is the one rounding rule: whole numbers from 10 up, one decimal from 1, and two significant figures below 1, so a real gain never rounds to 0. The operator's rule, the same day: "for the very early game, we can't round to 0, or the green arrows don't work for anything". My first cut used a fixed two decimals, which still zeroed anything under 0.005 (one point of armor for a Holy paladin). Only float noise under 1e-6 reads 0, so two identical items get no arrow, and a value never prints as "-0". math.log10 is present on Classic Era (Blizzard's own Blizzard_SharedXMLBase/Compat.lua:27 reads it). All three sites use it. The four tooltip strings (tooltip_upgrade, tooltip_ep, tooltip_spec_upgrade, tooltip_spec_upgrade_pct) take the EP as %s instead of %d, because %d truncates 0.22 to 0. The "vs. yours" columns, the loot roll window and the upgrade popup print through Loadout.DeltaText, which concatenates the number, so they show the decimals with no change of their own. Specs in Tests/scorable_spec.lua: the reported boots as a fixture, the tooltip string, and C.RoundEP's own cases; mutation-checked (whole-number rounding put back turned five red, one of them printing "Upgrade: +0 EP"). Nothing had pinned the old rounding: the whole suite stayed green when I changed it, before the new specs existed. Not verified in a client.

Fixed -- WoW Forever: hovering an action button raised a "secret number" error

Operator report from the Forever client, 16 repeats: Dibs/Data/ItemSources.lua:1859: attempt to compare local 'mainSpeed' (a secret number value, while execution tainted by 'Dibs'), reached from UpgradeTooltip:Augment through Loadout into equippedContext, which feeds UnitAttackSpeed("player") to Items.SwingsPerSec. Forever returns that speed as a secret value, and tainted addon code may not compare one. I wrote mainSpeed > 0 with no thought for a client that could do this.

SwingsPerSec now routes each hand through usableSpeed, which treats a value issecretvalue reports as secret the same as no weapon, so the item library falls back to its per-class swing default. issecretvalue is the documented addon-side test (warcraft.wiki.gg, Secret Values), read as a global at call time and feature-detected, so Classic Era and TBC, which do not have it, run the old path unchanged. Added to both .luacheckrc and .luarc.json, which Tests/ asserts stay one list.

Spec: Tests/attack_spec.lua "with a secret speed (WoW Forever)", mutation-checked (removing the check turned both cases red). Not verified in the Forever client. Other Forever reads that could come back secret have not been swept; this fixes the one reported.

Fixed -- planning an alt showed the logged-in character's gear

The known issue "switching character on the Build tab can leave the previous character's equipped gear on the paperdoll, and the gear lists may then show 'no matches'". Three places read the LIVE character where the PLANNED one was meant, all mine:

  • Every "else what's worn" fallback in GUI/Planner.lua (the slot icon, its tooltip, its chat link, the enchant row's piece and the upgrades-only baseline) called GetInventoryItemID("player"). They all go through one new PlannerUI.WornID(key) now, which reads the planned character's record for an alt. PlannerUI.PlanningLive() gates the two things only the live character has: SetInventoryItem("player") on the hover and GetInventoryItemLink for the chat link.
  • Loadout:EnsureCharDefault gave an alt an empty loadout, so every slot fell through to the live read. It now seeds from the alt's record.
  • Loadout:ResetToEquipped always rebuilt the logged-in character's default, so Reset on an alt jumped the Planner back to you. It takes the planned character's key now (the Reset button passes PlannerUI._planChar).

The record is new: Characters snapshots worn (, through Loadout:SeedFromEquipped) at login and on PLAYER_EQUIPMENT_CHANGED, which the module now registers. Its comment used to say it deliberately registered no equipment event, and that was right while no stored field moved on an equip; membership_spec pinned it and now pins the new set (six events, still not UNIT_INVENTORY_CHANGED). Characters:Worn(key) reads it back. An alt last seen before this version has no record and opens empty until it logs in once.

Specs: new Tests/altgear_spec.lua (5), plus two worn capture tests in membership_spec. Mutation-checked: making WornID ignore the planned character, and making Reset always live, each turn their test red. Not verified in a client. Not changed: the Planner's live stat panel (hit, crit, attack power) still reads the logged-in character's own stats from the game, which an alt cannot supply.

Also found stale while checking, and removed from the CurseForge page's known issues: "the 'no matches for this slot' message runs off the edge of the window" was already fixed (the hint is anchored on both sides and wraps, GUI/Planner.lua), and "the Wishlist doesn't say which boss drops an item" was too (the Wishlist has a Boss column filled from the item's drop source, GUI/Wishlist.lua sourcesFor). That empties the page's Planner and Wishlist known-issues list, so its heading went with it.

DKP: the Absences dialog is the library's form dialog too

ensureAbsenceDialog (GUI/DKPTab.lua) was the last hand-built dialog in the file, because its paragraph (the night, who signed up, who was absent, the penalty) is rebuilt on every open and the form dialog had nowhere to put changing text. LibAceGUIWidgets added opts.body / f:SetBody in the same MINOR 37 on request (DIBSREQ-LAGW-015), so the dialog is now W:CreateFormDialog with no rows: Refresh sets the paragraph with f:SetBody(text) or clears it with f:SetBody(nil) beside the refusal, and the dialog's height follows the text. Apply is the dialog's own OK button (f.applyBtn is kept as an alias to f.ok, tooltip unchanged) and calls f._apply, which is the old OnClick body unchanged. Same frame name DibsDKPAbsence, same confirm, same refusal sentences. The existing Absences specs in Tests/dkptab_spec.lua pass unchanged; mutation-checked by setting a fixed body, which turned both body tests red. No new release precondition: it needs the same MINOR 37 as the paragraph above. Not verified in a client, including whether the height is right on the first open, which the library flagged as unverified for a SetBody before the dialog has been drawn.

DKP: the Adjust and Season dialogs are the library's form dialog

The last of the LibAceGUIWidgets 33-37 adoption. ensureAdjustDialog and ensureSeasonDialog (GUI/DKPTab.lua) no longer hand-build frames; both are W:CreateFormDialog (MINOR 37, DIBSREQ-LAGW-013), same names (DibsDKPAdjust, DibsDKPSeason), same field aliases (charBox, decayBtn, clearBox and so on), same Record logic and the same parented confirm. The Amount row's "negative deducts" line is that row's note; the master looter's "this raid only" line is the Character row's note, set per open by SetScopeShown; the Season dialog's leaver tick is a kind = "check" row. The title now takes the library's accent instead of C.Colorize. DKPTabUI.CaptionClash walks every shown descendant rather than direct children, because the form dialog puts each row's button in a holder frame; hidden children are skipped, so a closed confirm's OK is not mistaken for a clash. Specs: Tests/dkptab_spec.lua reads the scope line from notes[1] (text included) and gains a test that the guard sees a row's button, mutation-checked by cutting the descent. (The Absences dialog followed; see the entry above.) Not verified in a client, including how the wrapped check label looks.

Tests: the Source-cell click finds its row through the library

Tests/questbookdraw_spec.lua's sourceOverlay now asks RowList:CellFrameFor (LibAceGUIWidgets MINOR 37, DIBSREQ-LAGW-014) instead of reading rl.rows and rl:_getSortedData(), as Peer Review asked (thread f0d06737). It errors rather than skips on a library without the method. Mutation-checked: asking for a column that does not exist turns all four Source-click tests red. Test-only.

All of v0.5.0, v0.4.7, v0.4.6, v0.4.5, v0.4.4, v0.4.3, v0.4.2 and v0.4.1, and every version from v0.4.0 back, have moved to CHANGELOG_ARCHIVE.md -- this file has to stay under GitHub's 125,000-character release-body limit, which Tests/pkgmetaclaims_spec.lua asserts at 120,000 rather than leaving to whoever happens to remember it. Released versions go across a few sub-sections at a time, oldest first; the archive's own header records each move.

This mod has no additional files