File Details
Housing Decor Guide v2.40.0
- R
- May 4, 2026
- 1.92 MB
- 15.9K
- 12.0.5+2
- Retail
File Name
HousingDecorGuide-2.40.0.zip
Supported Versions
- 12.0.5
- 12.0.1
- 12.0.0
* StylesTab refactor (plans/2026-05-04-stylestab-split.md):
HDG_StylesTab.lua decomposed from a 2920-line monolith to a 1656-line
orchestrator + a UI/Tabs/Styles/ folder of screen modules + a UI/Widgets/
folder of cross-tab widgets. -1264 lines (-43%) extracted across 8 phases.
- Phase 1: shared widget seam at UI/Widgets/ -- HDG_W_SegmentedChip +
HDG_W_TypePill. Replaces previous inline implementations.
- Phase 2: state extracted to UI/Tabs/Styles/HDG_ST_State.lua. Single
source of truth for all Styles selectors (selectedStyle, selectedTier,
collectionFilter, etc.) with named mutators (SelectTier, ResetForStyle,
SetCollectionFilter, etc.). 162 references migrated. Legacy
uncollectedOnly + collectedOnly boolean pair consolidated into
State.collectionFilter ("all" / "have" / "need").
- Phase 3a: ItemPanel extracted to UI/Tabs/Styles/HDG_ST_ItemPanel.lua.
Owns the right-side panel widget; Build/Render/LoadModel/Show/Hide/
GetFrame surface. Shims (RefreshItemDetail, LoadPreviewModel) forward.
- Phase 3c: Landing data layer extracted to UI/Tabs/Styles/HDG_ST_Landing.lua.
BuildData{ statsCache } returns the full landing row array
(curated + filtered + shopping lists + snapshots + sessions + room
concepts + collections). Widget creation stays in StylesTab.
- Phase 3d: Detail screen progressively extracted to
UI/Tabs/Styles/HDG_ST_Detail.lua across five sub-phases:
3d-1: helpers + cell handlers (TIER_TOOLTIP_LABELS, OnCellEnter/
OnCellLeave/OnCellClick, UpdateCellHighlights, HideFilterButtons,
SortByDecorName).
3d-2: shared flat-gallery renderer (RenderFlatGallery, used by both
snapshot + shopping-list views).
3d-3: Snapshot + ShoppingList render bodies (RenderSnapshot,
RenderShoppingList).
3d-4: Session render body (RenderSession).
3d-5: Main detail render (Render -- the tier/themed/curated/collection
branching with chip rendering + tier counts + filter loop +
view-mode dispatch). Plus card-grid, item-list row, and item-list
renderers; IsItemHidden, ItemHasRepRequirement, ItemCostsEndeavor,
SOURCE_FILTER_DEFS, ConstructItemListRow.
- Subtitle anchor fix: detailDesc now anchors to detailHeader's BOTTOMLEFT
instead of the frame edge -- previously the subtitle hugged the panel
border.
Remaining for a future session:
- CreateDetail widget construction (~250 lines)
- CreateLanding widget construction (~190 lines)
- Smart Set landing rows showing 0/0 + empty preview icons in some
states -- transient bug we couldn't reliably reproduce; needs a
focused session with the recipe captured.
* Style tab landing header reworded -- "Browse by Style (Beta - in development)"
becomes "Style, On Purpose -- Drag, drop, and act like that was the plan."
Beta marker dropped.
* Style tab detail view polish, curated cleanup pass:
- Curated styles no longer show the Signature/Accent/Versatile/Clashing
tier chips or the Great Hall/Kitchen/etc. room chips. Those filters
belong to Smart Set / themed styles where items actually classify by
tier or room; on a curated set they were always rendering "(0)".
- New CURATED / SMART SET / SNAPSHOT / COLLECTION / THEMED type pill next
to the title.
- Subtitle now reads "<N> items" + optional description (replaces the
standalone "Curated" line).
- Have/Need/All segmented chip replaces the Uncollected + Collected
checkbox pair.
- List/Grid view toggle moved out of the chip rows into the top-right
utility cluster next to Export.
- Curated/collection/snapshot detail views drop ~54px of vertical chrome
so the item grid starts higher.
* Fixed: empty rows appearing in Style detail grids when scrolling or
switching styles. HDG_StyleCardRow's shared cell pool was re-parenting
cells across recycled WowScrollBoxList rowFrames -- new rows would steal
cells from earlier rows, leaving the earlier rows visually empty. Each
rowFrame now owns its cells exclusively (no shared pool); ScrollBox's
built-in rowFrame recycling handles virtualization. UpdateStyleCellHighlights
rewritten to walk visible rowFrames via ForEachFrame instead of the
retired pool.
* New shared widget folder UI/Widgets/. First two residents:
- HDG_W_SegmentedChip (mutually-exclusive 3+ state chip control; powers
the new Have/Need/All filter)
- HDG_W_TypePill (small dim-uppercase header label; powers the new
CURATED / SMART SET pill)
Both designed for cross-tab reuse. Acquisition / Preview / Goblin filter
bars are candidates for future migration.
* Fixed: legacy customStyles weren't being restored from SavedVariables on
load, so the v1-collections migration ran with a nil source table and
silently copied zero styles. Affected anyone whose save predated the
v1-collections migration's first run (saves restored from backup, fresh
installs that imported old data, etc.). Saves where state.collections
was already populated were unaffected. Fix: load HDG_DB.customStyles +
customStyleOrder into state.* before Migration.RunAll, mirroring the
save-side preservation block.
* Data tab no longer spins up its own catalog searcher. The legacy refresh
flow (TriggerCollectionSearch) created a CreateCatalogSearcher with
SetOwnedOnly(false) + BasicDecor mode, but immediately filtered every
result through HDG_Data.GetCatalog — which only carries owned rows — so
the un-owned 4k+ entries were enumerated and discarded each click.
Replaced with a synchronous BuildCollectionSnapshot that iterates
HDG_Data.GetAllCatalog() directly. Refresh is instant, the panel
auto-populates on tab open, and HDG_Data is once again the only owner of
CreateCatalogSearcher in the addon. ~120 lines net deletion.
* Catalog Store-canonicalization complete (Steps 1-4). HDG.Store's
state.collection.decorCatalog and state.collection.catalogByItem are now
the single source of truth for catalog data; the module-local _catalog /
_catalogByItem upvalues in HDG_Data have been retired. Saves ~1.5-2 MB of
persistent duplication and eliminates the ~3-4 MB transient deep-copy
spike that fired on every sweep. External call sites (Acquisition,
Preview, Styles, HouseEditor, ZoneTracker, etc.) read through the same
HDG_Data.GetCatalog / GetCatalogByItem / GetAllCatalog accessors as
before — internal plumbing change, no behavioural impact. Mutators
(sweep finalize, UpdateSingleEntry, InvalidateCollectionCache) write
through Store directly with wipe-in-place semantics, preserving table
identity for short-lived caller handles.
* Login no longer rebuilds the catalog. The deferred-eager verification
sweep (kicked 0.5s after SavedVariables restore) was responsible for the
~85 MB cold-open memory spike: it re-read every catalog entry, re-built
~1,706 row tables, and re-populated variants to confirm what the persisted
save almost always already had right. Replaced with a lightweight ID-only
diff (VerifyOwnedSetIDsOnce) — a one-shot owned-only searcher enumerates
current decorIDs, compares to persisted ownedDecorIDs, and patches ONLY
the few that drifted via the existing UpdateSingleEntry path. No row reads
or variant population unless drift is actually detected. Catches the rare
cases (addon disabled mid-session, crash before save flushed, etc.) without
the per-login ~30-50 MB allocation churn. Drift during normal play remains
caught by HOUSING_STORAGE_ENTRY_UPDATED (per-entry) and HOUSING_STORAGE_UPDATED
(bulk, fires on housing customize entry). Refresh button still does a full
rebuild on demand.
* Zone scanner no longer triggers WarmCollectionCache at PLAYER_ENTERING_WORLD.
Persisted ownership data already lives in HDG.Store from SavedVariables by
the time PEW fires, so IsItemCollected / IsDecorCollected now fall back to
Store directly when the module-local cache hasn't been warmed. Result: the
addon does no eager work at login; the scanner answers from persisted state
for users with saved data, and silently no-ops on cold-cold sessions (first
install) where Store has nothing yet — they populate it the first time they
open the HDG window. IsCollectionCacheWarmed accepts Store data as warmed.
* Power Crafting raw-material totals are correct again when a crafted
sub-reagent is reached via two parent paths in the same recipe. The
dependency-graph walker was incrementing the revisited node's quantity but
skipping recursion into its children, so the second path's leaves were
lost. Player-reported repro: Kirin Tor Skyline Banner (8 Spellweave + 8
Moonshroud) was showing 80 Frostweave Cloth and 16 Infinite Dust; correct
totals are 160 and 32. Any recipe that pulls the same crafted intermediate
from multiple parents (Imbued Bolts, Heavy Borean Leather, etc.) was
understated by the same factor.
* Projects → Architect: Plan mode is live. Mode pill (Reflect / Plan) on
the canvas; Plan mode unlocks drag-to-reposition (snap-to-grid + footprint
collision rejection), right-click rotate (cycles 0/90/180/270 quarter-
turns), right-click context menu Remove (planned-only) / Reset position
(captured), and a left-dock palette of all 17 shapes that spawns a fresh
greenfield room on click. Manual cell positions persist via cell.locked
and override the auto-layout for both Reflect rendering and the lower-
floor backdrop. "Reset auto-layout" wipes locks for the current floor.
* Projects → Architect: manual room-connection assertion in Reflect. Click
any open-door orb → context menu of compatible rooms (same floor, opposite
cardinal, also unfilled). Right-click a room → Connections submenu showing
each cardinal's state with Disconnect actions. Asserted/blocked records
live in projects.connections[floorID]; _floorLayout seeds them before the
cardinal-matching algorithm runs, so manual overrides always win. Closes
the topology heuristic ceiling — players can hand-fix mistakes the
algorithm makes on ambiguous-cardinal houses.
* Projects → Crates: Library + Stamp. CrateDetail gains "Save as template"
(clones members + name into a new lib_<uuid> entry). RoomSidePanel gains
"Stamp template" (context menu of every library entry grouped by source —
My Templates / Curated). Click → mints a fresh crate cloned from the
template into the current room, opens its detail. Curated content data
file ships 7 starter templates (Vrykul Throne Hall, Cosy Hearthstone,
Tavern Essentials, Library/Study, Garden Sanctum, Winter Chalet, Vrykul
Armory) — seeded by an idempotent v3-curated-library migration that
remembers player deletes via state.curatedSeeded[key].
* Projects → Crates: orphan handling. Re-capture no longer cascade-deletes
crates whose room is removed; DeleteProjectRoom detaches them (parent=nil)
and they surface in any RoomSidePanel under "Unrooted crates (N)" with
per-row "Move here" buttons that adopt the orphan into the current room.
Crate_ListOrphans + Crate_Reparent helpers added.
* Projects → Crates: real decor picker. New modal HDG_CratePicker.lua
with sticky header, search box, status chips (Owned / Missing / Craftable
/ Vendor), top carousel (All + 6 Blizzard categories with progress bars +
My Styles + Templates cards purple-tinted), sub-carousel (subcategories
of active category with progress), virtualised row list with checkboxes,
3D model preview pane, and footer (selected count / Add visible / Done).
Styles and Templates cards swap the row list to a list-view mode that
shows each entry with [All N] [Missing N] bulk-add buttons, normalising
itemID-keyed Style members to decorIDs before adding to the crate. The
generic horizontal carousel widget (HDG_Carousel) and pure filter
primitives (HDG_DecorFilter) are factored as standalone modules so Style
Creator can adopt them later. CrateDetail's "+ Add decor" button replaces
the placeholder "by ID" text input.
* Projects → Crates: CrateDetail decor list virtualised via WowScrollBoxList
(no more rows overflowing the panel on large crates). Each row leads with
the decor's icon (resolved via C_Item.GetItemIconByID) and shows the
catalog name with a hover tooltip. Translucent lighter-blue overlay panel
(instead of opaque sidebar) so the canvas remains visible behind. HIGH
strata so room buttons can't bleed through.
* Projects → Architect: Plan-mode budget impact preview. Rooms meter shows
a translucent purple "planned" overlay on top of the actual spent bar;
text suffix reads "→N (+M)" when over budget. Per-shape budget pulled
from DB2 HouseRoom.WeightCost (closet=1, square_m=12, square_l=20,
octagon_l=16, etc.) so totals match GetSpentPlacementBudget exactly.
Decor count meter (GetNumDecorPlaced) added in Reflect, hidden in Plan.
Floor + house room-count chip top of canvas.
* Projects → Architect: room-scoped Crates. Each room gains a Crates section
in the right-side detail pane; "+ New crate" mints a path-keyed crate and
opens a detail editor (rename, member list, "+ Add decor by ID", remove
buttons). Crates persist via the new Collection_* API and survive reloads.
Decor Preview rows now accept Ctrl+Right-click to drop the row's item into
any crate via a context menu grouped by room. Plain right-click still
toggles favorite — the existing UX is preserved.
* Style Curator + Smart Set Builder: cleaner split between the two surfaces.
Style Creator is renamed Smart Set Builder and is now Smart-only — the
Curated mode pill is gone, and new styles always start as smart filters.
Curated style creation, editing, renaming, duplicating, and deleting all
live in the Style Curator now (right-click any target row for the menu).
Hero card subtitles on the Styles landing reframed to reflect the split.
Existing curated styles still open in the legacy editor view from the
landing list for backward compat, but the toggle is gone.
* Projects → Architect: clean rewrite of the room-layout pipeline. Pin-frame
data → player-cardinals via a single N↔S flip in FP.facingToCardinal
(Blizzard builds N→S internally but the editor view flips that axis on
screen). Replaced the two-phase BFS+DFS, rotation deducer, and
synthetic-door injection (~360 lines + a whole module) with a single-pass
BFS that reads captured door multisets directly. Pin frames only emit
internal connection doors and IsOccupiedDoor() honestly flags the
connected ones — the earlier confusion was the missing axis flip, not a
Blizzard bug. TopologyCapture now stores doorCardinals (every slot,
drives orb rendering) and occupiedCardinals (real connections, drives
BFS). Canvas anchors its host at the bottom so Entry sits at canvas south
and rooms fan upward. CaptureAllFloors forces a Decorate→Layout toggle
so pin frames re-emit on the first click; passive _onModeChanged
early-returns during a sweep so the toggle can't race the capture.
Removed: HDG_RotationDeducer, _preprocessImplicitDoors, the Entry-N
hardcode override.
* Style Creator: new "Audit" mode lets you triage decor that isn't in any
of your custom curated styles, fan items out into existing styles via
instant-assign with an undo strip, and audit existing style contents.
Filter the grid by Blizzard catalog category chips (Structural,
Furnishings → Seating, etc.). Coverage progress bar tracks the
shrinking gap as you triage. Multi-select via shift/ctrl/alt-click
for bulk-add. Inline "+ New style" affordance creates a target style
mid-session. (Synthea1979 request — for collections of 2000+ items
where it's hard to spot what hasn't been categorised yet.)
* Note (root cause for v2.39.1): traced to BigWigsMods/WoWUI commit
d0877949df, patch 12.0.5.67088 (2026-04-22). That patch added a new
Blizzard_HousingMarketProductDisplay.lua and inserted a synchronous
RestoreFilterAndFocusState call as the first line of
HouseEditorStorageFrameMixin.OnTabChanged. RestoreFilterAndFocusState
cascades into ScrollBox SetDataProvider → product card Init → calls
CatalogShopUtil.GetProductInfo (async, returns nil before
CATALOG_SHOP_DATA_REFRESH). The next ScrollBox Update runs Layout
on the same card and indexes self.productInfo.catalogShopProductID
on the nil. Any addon that holds a long-lived
C_HousingCatalog.CreateCatalogSearcher delays the market fetch
enough to expose this; bare client wins the race. This is a Blizzard
bug — Layout/Init in the new mixin lack a nil guard. Re-enabling
HDG's inline HE-storage-panel tab is gated on a Blizzard fix.
Documented at Reference/HOUSING_CATALOG_API.md (Catalog Tab Gotcha
section) and in the wow-api MCP under
HouseEditorStorageFrameMixin.RestoreFilterAndFocusState,
HousingMarketProductDisplayMixin, and
CatalogShopDefaultProductCardMixin.Layout.