File Details
Housing Decor Guide v2.43.0
- R
- May 10, 2026
- 1.99 MB
- 10.0K
- 12.0.5+2
- Retail
File Name
HousingDecorGuide-2.43.0.zip
Supported Versions
- 12.0.5
- 12.0.1
- 12.0.0
User-facing additions to the HouseEditor companion plus a heavy
internal-hygiene pass. Smoke-tested across all queue-add paths,
Acquisition + Recipes filters, post-/reload state restoration, and
the new HouseEditor strip + filter controls.
* HouseEditor companion: header search box (under the title) filters
the grid by item name across whatever view is currently selected.
Persists across mode switches; Esc clears; clear-X button to the
right of the box clears on click.
* HouseEditor companion: indoor/outdoor filter button (cycles
Indoor + Outdoor / Indoor only / Outdoor only) right of the
placement-cost icon. Filters the visible grid by each item's
catalog isAllowedIndoors / isAllowedOutdoors fields.
* HouseEditor companion: per-cell placement-cost badge in the
bottom-left of every grid card, using the same budget-icon atlas
as the Decor Preview detail panel. Toggle on/off via the icon to
the right of the title; greyed out when off.
* HouseEditor companion: cards for items with 0 copies in storage
(everything you own is already placed) are now greyed out + slightly
desaturated, so "you own this but cannot place another" is visible
at a glance.
* HouseEditor companion: "By Placement Cost" virtual collections
appear at the bottom of the Collections sidebar -- Small (1-2),
Medium (3-4), Large (5), X-Large (6+). Click any bucket to see all
decor in that cost range. Header rows in Themes and Collections
also picked up an underline for cleaner section divides.
* HouseEditor companion: "Recent placements" strip across the bottom
shows your last 8 placed items. Newest enters from the right; older
shifts left as new placements come in. Reuses the same card chrome
as the main grid (tooltips, badges, dye markers all work). Refreshes
live as placements happen in the editor.
* HouseEditor companion: window background is now opaque (chat /
world no longer bleed through behind the chest atlas).
* Acquisition tab: collapsible Advanced Filters panel + preset
buttons. Reduces visual noise on the left filter row and gives
one-click access to common filter combos.
* Fix: crafting-queue items now persist their `profession` field through
the Store on insert. Previously HDG_PowerCrafting.MakeQueueItem set
it but Store:AddToCraftingQueue dropped it on the way to
SavedVariables, so post-/reload `GetCharactersWhoKnowRecipe(spellID,
profession)` calls passed nil and crafter-grouping in the queue
picked from any profession that knew the spell instead of the
recipe's actual profession. Row icons recovered via the itemID
fallback so this was visible-only after a reload, but the grouping
was less precise. Single-line fix at HDG_Store.lua:725.
* Refactor: crafting-queue mutations now have a single canonical
refresh path instead of two. Previously every Add/Remove/Update/
Clear dispatched through Store -> EventBus (which refreshed
QueuePanel + MaterialsPanel) AND then explicitly called
HDG_UI.RefreshUI() (which refreshed QueuePanel + MaterialsPanel
again, plus RecipesTab). The materials aggregation is the
expensive bit -- it walks the whole queue summing reagents -- so
the duplication was meaningful per click. EventBus is now
authoritative: it refreshes queue + materials + recipes (added
recipe refresh so in-queue badges still update); the post-dispatch
HDG_UI.RefreshUI() calls were dropped from
HDG_RecipesTab:AddToCraftingQueue and the four queue-mutation
paths in HDG_QueuePanel (remove, shift-remove, decrement, clear).
Expand/collapse paths in QueuePanel still call RefreshUI directly
-- they don't dispatch and so don't trip the EventBus.
* Refactor: HDG_AcquisitionTab:SelectItem split into a small
orchestrator + three mode-specific helpers. Was a 75-line
if/elseif/else with three branches that each repeated the same
4-line detailPanel anchor reset, the same UpdateHeaderPanel call,
and (for the crafted/non-vendor cases) the same vendor-state
clear + waypoint/map hide + cell release. Now SelectItem owns
the shared prelude (selectedItemID, headerPanel/detailPanel show,
detailPanel anchor reset), data probe (vendors / hasVendors /
isCrafted), single dispatch (vendor wins over crafted, matching
original priority), and shared postlude (UpdateHeaderPanel,
UpdateNoteDisplay, vendor-note widgets, cart buttons,
UpdatePreviewModel, UpdateRowHighlights). The three new methods
-- :SelectVendorItem, :SelectCraftedItem, :SelectNonVendorItem --
each own only the work that actually differs by mode. ~30 lines
of duplication gone; behaviour preserved.
* Refactor: same surgery on the Acquisition tab. HDG_AcqData.filters
was a public mutable table on a "data" module that the Acquisition
UI mutated directly (HDG_AcqData.filters.search = val,
.zone = val, .source = val etc.) -- worse smell than the Recipes
side because there weren't even setter wrappers; the UI just grabbed
the table and wrote to it. HDG_AcqData.sortMode same shape.
Both now live as module-locals (acqFilters, acqSortMode) on
HDG_AcquisitionTab where they belong. HDG_AcqData's three filter
consumers (RebuildFilteredItems, RebuildFilteredVendors,
ItemPassesFilters) take filters as a parameter -- pure data-layer
logic, no shared mutable globals. Signatures changed:
RebuildFilteredItems(CONFIG) -> RebuildFilteredItems(filters, sortMode)
RebuildFilteredVendors(CONFIG) -> RebuildFilteredVendors(filters)
ItemPassesFilters(itemID, CONFIG) -> ItemPassesFilters(itemID, filters)
CONFIG was dead in all three after the itemFacts refactor (used to
feed BuildSourceMatchers which is now data-driven). Behavior
preserved -- filters still session-only, reset on /reload, same
defaults. ~30 mutation sites in HDG_AcquisitionTab updated;
external API surface (BuildCaches still takes CONFIG) unchanged.
* Refactor: pulled recipe filter state out of HDG_Data and wired
consumers to read Store directly. The 14 thin filter accessors
in HDG_Data (GetSearchFilter / IsUncollectedFilterEnabled /
IsCraftableFilterEnabled / IsKnownFilterEnabled / Get/SetExpansion
Filter / IsSearchReagentsEnabled / Get/SetProfessionFilter and
their Set* siblings) were zero-abstraction passthroughs to
HDG.Store:GetConfig / Dispatch -- they didn't validate, transform,
log, or hide anything; they just relocated the call into a "data"
module that has nothing to do with UI filter state. Side-effect:
RecipeMatchesFilters used to read those globals plus a module-local
`recipeSearchFilter` that lived in HDG_Data only because the Edit
Box and the matcher happened to share the file. The matcher now
takes a filterContext (search / searchReagents / uncollectedOnly /
expansion / knownOnly); HDG_RecipesTab owns the search-box state as
its own module-local + a BuildFilterContext() helper that pulls the
rest from Store. HDG.Store gets two new methods --
GetCharacterProfessionFilter() / SetCharacterProfessionFilter() --
that hide the per-character key lookup so callers don't duplicate
HDG_Data.GetCharacterKey() + nil-guard chains. Net: HDG_Data shrank
by ~14 functions + the `recipeSearchFilter` local + two unused
GetConfig/GetUIState helpers; data-layer logic is now pure (takes
filter state as parameter); state ownership is unambiguous (Store
for persisted toggles, RecipesTab for transient search text).
Sets up future HDG_Data domain split with no cross-module filter
entanglement to untangle.
* Perf: scoped style-cache invalidation. The EventBus handler at
the bottom of HDG_StyleEngine.lua used to call
InvalidateCache() (full nuke -- vocab, facetStore, reverseIndex,
every per-style bucket) on every HDG_COLLECTION_UPSERTED /
DELETED / DECOR_ADDED / DECOR_REMOVED / FIELD_CHANGED event with
payload.type == "style". So adding a single decor to a single
curated style burned ~237ms (60ms steps 1-6 + 177ms step 7) on
the next StylesTab:Show even though steps 1-6 don't depend on
style definitions at all and ~99% of the per-style buckets were
untouched. Now: derive the styleId from payload.collID
("style:..." -> "...") and dispatch to the existing scoped
helpers -- InvalidateCustomStyle for mutations (rebuilds just
that style's bucket + clears its compatCache slice),
RemoveCustomStyle for deletes. Steps 1-6 stay valid forever
(until /reload), the other ~100 styles' buckets stay built,
next Show is instant. Guarded with `if not cacheBuilt then
return end` so events fired before the first StylesTab open
are no-ops (BuildCache picks up fresh state on first show
anyway via RebuildAllStyleDefs).
* Perf: per-refresh memoization for HDG_Data.IsItemCollected on
the Acquisition rebuild path. Item collection state is volatile
(changes on collect events) so it can't go in itemFacts, but
within a single RefreshList cycle it's called multiple times
per item: ItemPassesFilters (when uncollectedOnly is on),
RebuildFilteredItems' filteredUncollectedCount walk,
RebuildFilteredVendors' per-vendor counters (an item sold by
N vendors used to do N live calls), and pushItem's
uncollectedCount in RefreshVendorList. Cache is wiped at the
top of each Rebuild* call so each refresh sees fresh state;
downstream consumers in the same cycle hit warm cache.
Tooltip path (HDG_AcqRows row 668) intentionally left on the
live API -- hover-time should reflect current state.
HDG_AcqData.IsCollectedCached / ResetCollectedCache are the
helpers; cache is private to HDG_AcqData.
* Perf: ItemPassesFilters reduced to mostly indexed reads. New
HDG_AcqData.itemFacts table populated by BuildItemFacts (called
at the end of BuildCaches) precomputes per-item static flags
that don't change during a session: hasVendor, isCrafted,
hasSource, isDyeable, releasedOK, srcType/altSrcType, and the
per-source-category booleans (isAchievement / isQuest / isDrop
/ isTreasure / isPromotional / isShop / isReputation /
isEndeavor / isGoldOnly). The rewritten ItemPassesFilters reads
flags from the cache for everything except the inherently
dynamic checks (IsItemCollected, search match, vendor-side
expansion/zone/faction match -- those depend on filter values
so can't be pre-baked). The rewritten SOURCE_MATCHERS each
collapse to `function(f) return f.isX end`. The biggest wins:
Reputation / Endeavor / Gold Only source filters were doing a
per-item npcID walk on every call; releasedOnly was doing a
per-item zero-cost vendor walk on every call; both are now
single boolean reads. Trade-off: BuildItemFacts adds a one-time
pass at BuildCaches time. Names that haven't loaded by that
point fall through the legacy "Unreleased / Loading..."
heuristic at snapshot time, which means new releases between
patches don't update releasedOK until /reload -- accepted
because retail item-release cadence is patch-day, not daily.
Measured before/after on retail with 1676 items in scope:
vendor mode RebuildFilteredVendors dropped 30-50%
(full-scope 2.86 -> 1.76ms; heavy-filter 1.5-2.0 -> 0.6-0.75ms).
* Perf: vendor-mode RefreshList no longer double-filters every
item. Was running both RebuildFilteredItems (full universe-wide
pass: ItemPassesFilters * allItems + a sort) AND
RebuildFilteredVendors (which already runs ItemPassesFilters on
every item-per-vendor and populates vendorFilteredItems) on
every keystroke / filter toggle / search update in vendor mode.
Vendor mode now skips the universe-wide pass and derives the
status-bar / header item count from a post-loop distinct walk
over vendorFilteredItems (set as HDG_AcqData.filteredItemCount).
By Item mode is unchanged. Map All button now calls
RebuildFilteredItems(CONFIG) on click to keep its waypoint
iteration correct in vendor mode where filteredItems is now
intentionally stale -- one-time cost on a rare click. Added
Acq.Rebuild.FilteredVendors to HDG_Debug.Timed for before/after
measurement of this change and an upcoming itemFacts precompute
refactor.
* Refactor: code-smell sweep prompted by the 2.42.1 reagent-quantity bug.
Renamed in-memory recipe objects' `id`/`spell` fields to `itemID`/`spellID`
for readability (~40 sites). Renamed crafting-queue payload fields to
match (`{id, spell}` -> `{itemID, spellID}`) with a one-shot SavedVars
migration on load -- existing queued items are rewritten in place,
no data loss. Stripped dead `or 1` and `or mat.total` fallbacks that
were hiding bugs behind silent defaults. Consolidated six queue-item
builder sites (Recipes, Mogul, Acquire, Preview, Vendor List, Styles)
through a single `HDG_PowerCrafting.MakeQueueItem` helper. Mogul tests
rewritten to match new field names; 28/28 still green.
* Refactor: HousingDecorGuide.lua slimmed from 1806 to ~370 lines (-79%).
Per-domain Blizzard event hooks now live in their owning modules'
`RegisterEvents()` on their own event frames instead of being routed
through one giant OnEvent dispatcher in the main file:
- shopping-list `BuyMerchantItem` decrement -> HDG_VendorShoppingList
- mail/merchant/bank/trade UI suspend + lumber achievement + logout
session save + PEW lumber init -> HDG_LumberTracker
- tooltip "Housing Decor reagent" tag -> HDG_PowerCrafting
- crafting queue auto-decrement on craft -> HDG_PowerCrafting
- profession-cache invalidation + recipe-learn UI refresh + craft
acquisition log -> HDG_Data
- HOUSING_STORAGE_ENTRY_UPDATED repaint + QueueInventoryUpdate +
QueueRecipeUpdate fan-outs -> HDG_UI
- HOUSE_DECOR_ADDED_TO_CHEST log + housing-achievement display
refresh -> HDG_DataTab
- chattiness-gated talking-head craft quote + counter -> HDG_Vamoose
- ZONE_CHANGED_NEW_AREA: main file debounces and broadcasts
HDG_ZONE_CHANGED on the bus; LumberTracker, LumberHUD, and
ZoneMode subscribe themselves
Main file now only handles lifecycle/coordination events
(ADDON_LOADED bootstrap, PEW login-restore, BAG_UPDATE inventory
fan-out, PLAYER_LOGOUT Store flush) and orchestrator init calls.
* Refactor: slash command cleanup. /hdg is the user-facing slash
exclusively; all 12 dev/diagnostic commands (collection, featuredtest,
costtest, tsm, debuglumber, debugprof, exportreagents, testquote,
scantags (was tags), order, plus /lumberhud test/show/testdist
aliases) live exclusively on /hdgdebug now. Bodies fully extracted
from main file into HDG_Debug.lua; no delegation. /hdg help banner
added listing user commands. /hdgdebug exportreagents reuses the
existing HDG_Debug copy-window infrastructure -- the bespoke 50-line
popup frame and three associated globals (HDG_ExportReagents,
HDG_ShowExportWindow, HDG_ReagentExportFrame) are gone.
* Refactor: privateDB.pendingCraftInfo (multi-craft tracking, used by
RecipesTab when initiating a craft and by HDG_PowerCrafting's queue
auto-decrement when crafts complete) initialised in HDG_PowerCrafting
instead of the main file. Dead duplicate _runtime.pendingCraftInfo
removed from the Store state defaults.
* Refactor: dead bridge functions HDG:RefreshUI, HDG:RefreshCraftingQueue,
HDG:RefreshMaterials, HDG:RefreshLumberTracking, HDG:Toggle removed.
Zero internal callers; aspirational "for legacy macros" comment.
Macro authors should bind /hdg or /click an addon button instead.
* Refactor: HDG_RecipesTab.lua slimmed from 3876 to 1109 lines (-71%).
Four self-contained chunks lifted out into their own modules; what
remains is the recipe list (left column), the lifecycle methods
(Init/Create/Refresh/Show/Hide) and the EventBus glue:
- UI/Tabs/HDG_CraftDialog.lua (282 lines) -- the [Craft] button's
confirmation modal (frame creation + Show/Execute). Internal
caller updated to privateDB.HDG_CraftDialog.Show; no third-party
hooks exist so the old HDG_RecipesTab.ShowCraftConfirmation /
.ExecuteCraft surface was deleted, not aliased.
- UI/Tabs/HDG_ProfessionSidebar.lua (380 lines) -- left rail of the
Recipes tab: expansion-filter dropdown, "All Professions" button,
per-profession buttons with collection + recipes-known badges +
open-prof-window icon. Was HDG_RecipesTab:RefreshProfessionTabs
and the local EnsureProfessionTabFrames; now exposes Init(frame)
+ Refresh(). HDG_UI.RefreshProfessionTabs delegates to the new
module instead of RecipesTab.
- UI/Tabs/HDG_QueuePanel.lua (917 lines) -- middle column of the
Recipes tab: queue ScrollBox + char-grouping + expand/collapse
subsidiary recipes + the embedded Lumber widget at the bottom of
the panel. Was HDG_RecipesTab:CreateQueuePanel +
:RefreshCraftingQueue + ~580 lines of element kinds, binders,
DataProvider push helpers, plus several queue-only locals
(OpenRecipeAndThen, IsPSLAvailable, GetQueueItemColor,
GetProfessionForItem, expandedQueueItems,
pendingTooltipRebuilds + GET_ITEM_INFO_RECEIVED listener,
SUB_INDENT/SUB_HEIGHT). Exposes Init(frame), Create(), Refresh().
HDG_UI.RefreshCraftingQueue delegates to the new module; the
EventBus HDG_STATE_CHANGED handler that fans out queue refreshes
now calls privateDB.HDG_QueuePanel.Refresh() directly.
- UI/Tabs/HDG_MaterialsPanel.lua (1217 lines) -- right column of
the Recipes tab: materials ScrollBox + Totals/By-Recipe sort
toggle + Direct/Raw mode toggle + cost rollup + "Add All to
Auctionator" CTA. Was HDG_RecipesTab:CreateMaterialsPanel +
:RefreshMaterials + :AddAllMaterialsToAuctionator + 7 binders
+ 3 push helpers + the orphan UpdateCostDisplay local. Exposes
Init(frame), Create(), Refresh(), AddAllToAuctionator().
HDG_UI.RefreshMaterials and HDG_UI.AddAllMaterialsToAuctionator
delegate to the new module. Stripping these helper sites also
surfaced four genuinely-dead locals in HDG_RecipesTab
(GetConfig, GetUIState, GetCraftingQueue, GetRecipeByItemID --
now read only by the new sub-panels) which were removed.
* Refactor: helper de-duplication across the recipes-tab modules.
CreatePanelHeader (the 22px textured strip + title FontString) was
copy-pasted in three sub-panels after the split; lifted into
HDG_UIHelpers.CreatePanelHeader and the three local copies removed
(~90 lines of pure duplication gone). Three new read accessors on
HDG.Store (:GetConfig, :GetUIState, :GetCraftingQueue) replace the
hand-rolled "state and state.config and state.config[key]" walks
that every UI module had been duplicating; combined with calling
HDG.Store:Dispatch directly, the four recipes-tab modules dropped
~70 more lines of boilerplate Get/Set wrappers. CraftDialog: 282 ->
269. QueuePanel: 917 -> 874. MaterialsPanel: 1217 -> 1166.
RecipesTab: 1109 -> 1064. ConfigTab/DataTab/MogulTab finished in
the same style: wrapper preambles dropped, callers use HDG.Store
directly (-54 more lines, -2 unused-local lints).
* Refactor: new HDG_UIHelpers.CreateAtlasButton(parent, atlasBase,
size, opts) factory for the "frame + Normal/Pushed/Disabled atlas"
pattern that recurs ~30 times across the UI. Auto-suffixes the
Pressed and Disabled variants from atlasBase by default; opts let
callers override per-button. Retrofitted across the UI:
DecorPreviewTab destroy/minus/plus buttons, VendorShoppingList
cart-plus/cart-minus, TrainersTab + AchievementSection collapse
arrows, ZoneMode gear + clear-X icons, AcquisitionTab vendor + item
note pencils. Sites that don't fit (bind-time atlas swaps, custom
suffix conventions like HouseTab's -default/-active, or icons whose
Normal atlas is set later) left inline.
* Refactor: materials-panel toggles now use the canonical
HDG_UIHelpers.CreateFilterButton / UpdateFilterButtonState helpers
instead of inline backdrop+text-color juggling. Affects the Sort
Mode (Totals / By Recipe) chip and the Direct / Raw mode chip-pair.
Two side benefits: the active/inactive state colours now match
every other filter chip in the addon (was using bespoke
button_hover/panel + accent-border instead of the addon-wide
button_active/button_inactive theme tokens), and the panel shed
another ~56 lines (1166 -> 1110) of inline frame setup.
* Memory: feedback_no_defensive_or_cascades.md sharpened with
Vamoose's directive "things set to 1 hide bugs" -- default to no
`or N` defaults at internal boundaries; only keep them at genuine
external boundaries (WoW API returns, optional user config).