## Version 2.7.0 (July 2026)

**Added (12.1 PTR)**
- Dye herbs in your bags now wear small family-coloured dots (bottom-left of
  the icon) showing which Housing Dyes they craft -- multi-family herbs
  like Writhebark wear up to three. Works in the default bags, Bagnon, and
  Baganator. Hovering any dye herb also lists its dyes in the tooltip
  ("Crafts Purple / White Housing Dye -- 10 herbs = 1 dye"). Both are
  Settings toggles (thanks amycakesprima for the idea).

**Changed**
- "Snapshot" is now "Save Dyes" -- and everything it touches speaks the same
  word: the saved list is "Saved Dyes" (was "Saved Variants"), the option is
  "Auto-save dyes on Apply" (was "dye combos"), and the tooltips explain the
  loop ("they'll be one click away next time you select this decor type").
  Thanks ReganB for flagging the mystery button.
- New: "Auto-stage newest" toggle on the Saved Dyes panel (off by default).
  With it on, selecting a decor whose type has Saved Dyes pre-loads the
  newest entry as pending changes -- dyeing 20 identical bookshelves becomes
  one Apply each. Preview only: nothing is spent until you Apply, and Revert
  clears it (thanks ReganB for the nudge).
- The Canvas tab speaks "Saved Dyes" too: the chip wall header (was
  "Loadouts (N/10)"), the save popup ("Save dyes as:", default names now
  "Saved N"), and the tile tooltips all match the live editor's language.
- Auto-save dyes on Apply is now ON by default (existing installs get it
  switched on once with this update -- flip it back off in the cog if you
  prefer manual saves). The Apply tooltip notes when auto-save is active.

**Changed (12.1 PTR)**
- The Herbs tab opens as a one-line-per-family overview (like a certain
  excellent crafting addon): each family row leads with its dye's icon and
  reads "RED - craft 20g/dye (Rain Poppy) - AH 35g - can craft 4". Click to
  expand a family into its full herb list; the +/- chevrons match the Stock
  tab. Individual herb rows dropped their redundant dye icon.
- The "Sort: Expac" button is gone from the Herbs detail panel -- the detail
  text gets the full width instead. Expanded families always list herbs
  cheapest-per-dye first (the sensible order the button defaulted to anyway).
- Clicking a family header on the Herbs tab now fills the detail panel with a
  family summary -- the dye's name in its colour, shade and herb counts, how
  many you can craft right now, the cheapest herb route per dye, the dye's
  own AH price, and the click / Shift-click / 10-herbs-per-dye help line.
  Hovering a header shows a proper tooltip too (it was showing a stray
  "Dye" tooltip). Selecting a tiered herb (Saxifrage, Mycobloom...) shows
  stock and craft counts summed across its quality tiers, matching the row.
- The Herbs tab is now a dye-cost planner: each family's header shows the
  cheapest craft cost per dye next to the dye's own AH price and how many you
  could craft from herbs on hand; herbs list their cost per dye (10 herbs)
  and sort cheapest-first; herbs accepted by 2-3 families wear an x2/x3 badge
  -- those are the best farm targets. Shift-clicking a herb or a family
  header searches the AH (previously it only filled the search box without
  searching, and only worked for dyes).
- Craft (Herbs) planning understands the dye station: it allocates your owned
  herbs across families without double-counting shared ones (your Writhebark
  can serve Black OR Brown, not both), plans in whole 10-herb crafts, and
  keeps planning even when the AH has no price data -- the "Herbs needed"
  panel no longer comes up empty on a fresh economy.
- Expanding a family on the Stock tab now shows its shades as a simple swatch
  list -- the family header row owns the numbers and the +/- target editor
  (each shade was repeating the family's have/target/need, which read as
  buying that amount PER shade). Hovering a family header explains the deal
  -- "one Purple Housing Dye applies any of its 11 shades, each application
  consumes 1" -- plus the counts and the Shift-click x10 shortcut; hovering a
  shade names the dye that applies it.

**Fixed**
- Shift-clicking a dye row, herb row, or Herbs-tab family header now runs a
  default Auction House Browse search (with the AH open) -- the handlers were
  reading each other's fields, so shift-click had silently stopped doing
  anything; and quick lookups no longer detour through Auctionator's shopping
  tab (Send-to-AH still uses Auctionator). Shift-click searches now work from
  every tab (they used to apply the dye instead in live/Canvas/palette-edit
  modes), search the real item name on 12.1 ("Purple Housing Dye", not the
  colour), send the clean herb name (the x2/x3 badge markup was riding into
  the query), and switch the AH to the Buy tab if you were on Sell.
- Auto-save no longer renames your hand-named Saved Dyes: re-applying a combo
  that matches "Library blue" keeps that name (machine stamps like
  "Auto-saved 08:08" only name brand-new entries; the save/rename popups
  still rename, since that's you typing).
- The rename popup says "Rename saved dyes:" (last "loadout" holdout), and
  the Apply tooltip only promises an auto-save when one will actually happen
  (clear-only commits save nothing).
- 12.1: searching the Herbs tab now opens the matching families -- typed
  matches were being computed and then hidden under the collapsed one-line
  overview, so search looked broken.
- 12.1: fixed a crash entering House Editor customize mode while the Stock
  tab was active (the live dye picker re-derived a pre-family pigment
  grouping and errored; on 12.0.7 the same flow showed a pigment shopping
  list instead of the normal dye list).
- 12.0.7: per-colour stock overrides are no longer wiped on upgrade -- they
  keep working on the live client; only 12.1 retires them (a family stack has
  no per-colour unit to hold).
- 12.1: hovering a Stock family header now reports the family's true shade
  count -- previously it counted whatever the search box / owned-only filter
  left visible ("applies any of its 1 shades" while searching).
- 12.1: selecting a shared herb (Writhebark, Fireweed...) in the Herbs tab
  now lists every family dye it crafts in the detail strip, instead of an
  arbitrary one that could contradict the section you clicked.
- Item names resolve more reliably: the declarative async-name pipeline had
  been dead since it shipped (load-order capture), and a failed item load
  could stick a row on "Retrieving data" until /reload -- both fixed.
- Help window (?) refreshed: documents the family-header xN owned counts and
  the shift-click AH search; the retired per-row count-cell text is gone.
- Stock targets on the 12.1 PTR now mean what the header says: "target 3
  Purple" plans for 3 Purple Housing Dyes, not 3 per colour (the cart was
  demanding a family's colour-count times the target).
- Hardened the new colour sorting and the Apply gate against dyes with
  partial game data (no swatch or item), which could previously blank the
  Colors tab, the progress bar, or the Apply button.
- On the 12.1 PTR: Send-to-AH no longer builds un-matchable searches from a
  colour name before item names finish loading; herb expansion tags read from
  the herb itself instead of its list position; and hovering a herb names the
  family dye once ("Purple Housing Dye") instead of listing every shade.
- Clicking a harmony suggestion now also clears an active search so the jump
  always lands (companion to the family-filter fix).
- The "Colors: N/M" counter (and other auto-sized labels) no longer truncate
  to "..." when their text grows -- labels were measuring their already-clipped
  width, so once squeezed they could never widen again (spotted on the 12.1
  PTR where the count grew to 77/77).
- Clicking a harmony suggestion from a different colour family now follows
  through: the family filter switches to the picked dye's family so the list
  scrolls to and highlights it, instead of silently selecting something the
  active filter was hiding (thanks Gnuclear Gnome).

**Fixed (12.1 PTR)**
- All dye counting now pools by the dye item, ready for 12.1's family dyes
  (where every colour in a family draws from one shared stack):
  - Live mode's Apply button now correctly blocks (with the red warning) when
    you stage more colours from one family than you hold dyes for. Previously
    each colour checked the shared stack independently, Apply lit up green,
    and the game silently reverted the unaffordable channels on commit.
  - The Stock tab no longer multiplies a family's stack by its colour count --
    group totals, the deficit summary, herb requirements, and the Send-to-AH
    list now count each dye item once, with per-colour targets summing onto
    the family item. On 12.0.7 nothing changes (every colour is its own item).

**Changed**
- The Herbs tab speaks 12.1 on the PTR: herbs group under the nine Housing
  Dyes with the station's real herb lists, and herb details name the family
  dye instead of a pigment.
  On 12.0.7 the pigment view is unchanged.
- Ownership at a glance, redesigned for the family model (and live now on
  12.0.7): each Colors-tab family header shows the dyes you hold for that
  family ("11 shades  x9"), colours you can't apply yet are dimmed, and the
  per-row "xN" badges are gone -- exact per-colour counts stay in the detail
  strip and tooltips. When Apply is blocked, the tooltip now names the dye
  ITEM you're short of ("Purple Housing Dye" on 12.1) instead of listing
  every staged colour.
- On the 12.1 PTR, Craft mode now prices dyes the way the new dye station
  actually works: 10 herbs craft directly into 1 family dye (no pigment
  step), using the station's real per-family herb lists -- every accepted
  herb from Classic through Midnight, all quality tiers. On 12.0.7 the
  pigment recipes are unchanged.
- The Stock tab now targets the nine Housing Dye families too: group targets
  re-key automatically on first login (Teal folds into Blue, Pink into Purple,
  Grey into Black -- keeping the higher of the two targets). On 12.1 clients
  per-colour overrides are retired (stock is bought per family dye); on
  12.0.7 they keep working unchanged.
- The Colors tab now groups colours by their 12.1 Housing Dye family (Red,
  Orange, Yellow, Green, Blue, Purple, Brown, Black, White) -- the same nine
  sections the dye station uses -- and orders each section as a paint-strip
  ramp: by hue, then light to dark. The filter chips, the header progress
  bar, and the detail strip's "Color:" label follow the same families. On
  12.0.7 this is purely a new organisation of the same colours.
- Auto-Harmonise now works from each dye's own hue instead of rounding to one
  of eight colour buckets. Suggestions get noticeably smarter at the edges:
  a red-orange like Firebloom Red now gets its true blue-teal complement
  (previously plain green), and its triad lands on the classic
  orange-green-violet triangle. The classic pairings (orange-blue,
  red-green, yellow-purple) are unchanged.
- Harmonise suggestions are now pure colour theory: owning a dye no longer nudges
  it ahead of a better colour match in the suggestion ranking. Ownership is still
  shown (dimmed swatches, tooltips) and still checked when you hit Apply.

**Fixed**
- TOC now declares the correct 12.1 PTR interface version (120100), so VDS no
  longer shows as out-of-date on the PTR client.

## Version 2.6.0 (June 2026)

**Added**
- Dye-channel coverage: each dyeable decor now shows, per channel, how much of
  its surface that channel covers and what part it dyes. Canvas and Live mode
  display it as a readout on each channel -- "Part - Material - NN%", for
  example "Gilt frame & basin - Gilt - 65%". Built from the decor models'
  dye masks, covering 180 decor.
- Harmonise is now area-aware. It places the lead colour on the largest surface
  and the supporting colours on the smaller ones, so the result reads as a
  balanced scheme rather than three unrelated dyes.
- "Apply all" harmonise mode (toggle): re-dyes every channel from one starter
  dye you pick, balanced by surface area, instead of filling only the other
  channels. The toggle is in the Canvas harmonise menu, the Live edit header,
  and the settings sidebar.
- Live mode's gallery tiles show a small corner colour chip, tinted to the
  area-weighted average of the decor's applied dyes.

## Version 2.5.0 (June 2026)

**Added**
- Support for patch 12.0.7's new dyeable decor -- 13 new items are now
  recognised, so their dye channels show in Canvas and Live mode. 5 decor that
  Blizzard made non-dyeable in 12.0.7 (4 Founder's Point pieces and the Mounted
  Tavern Lantern) were dropped.
- Sneak peek: the upcoming Lunar Celebrant's housing set (Aquarium, Cradle,
  Privacy Screen, Ornate Vanity, Wide Pillow Roll) now appears in the Canvas
  decor picker, so you can preview dye combinations on it ahead of release --
  before it's available to collect.

**Fixed**
- The Canvas 3D preview now keeps your zoom and rotation when you change dyes
  (Random, Harmonise, or picking a dye for a channel). Before, any dye change
  rebuilt the model and reset the camera to the default view.
- The recent-dyes strip now re-orders when you click one of its swatches -- the
  re-applied dye moves to the front (most-recently-used first). Before, only
  picking from the main dye list re-ordered the strip, so the dye you just used
  could stay stuck mid-list.

## Version 2.4.0 (June 2026)

**Added**
- Live-mode keybindings for Apply and Revert (suggested by ReganB) -- assign
  keys in Options > Keybindings, under a "Vamoose's Dye Studio" section. Each
  presses the matching button, so it only fires when that button is active
  (Apply needs pending, owned changes; Revert needs pending changes). The Apply
  and Revert tooltips show your bound key, or a reminder to set one.

**Changed**
- The live-mode Apply button is now green so it stands out as the primary
  action next to Revert and Snapshot.
- The Colors harmony detail view is tidier: each strategy (Neutral, Mono,
  Analogous, ...) is now a contained card with its label as a header, laid over
  the selected dye's own two-tone gradient wash.
- The Canvas Harmonise menu now describes each strategy on hover -- its effect
  plus the same proportion "area guide" bar as the Colors detail tooltip, so the
  full guidance is where you actually trial harmonies.

## Version 2.3.0 (June 2026)

**Added**
- **Auto-Harmonise** -- pick any dye and Dye Studio now suggests colour
  companions for it: harmonious neighbours, the bold complement, balanced
  triads, and deliberate clashes, plus neutral grounding tones. It works
  wherever you dye:
  - **Colors tab** -- selecting a dye shows a band of harmony swatches beneath
    it, laid over a watercolour wash of that colour so you can judge each
    pairing against the real thing. Click a swatch to jump to that dye.
  - **Live mode** -- a strip of strategy buttons (Neutral, Mono, Analogous,
    Complement, Split, Triad, Clash) fills the decor's other channels around
    your focused channel in one click. A neutral main colour swaps the buttons
    to Tones / Contrast / Accent.
  - **Canvas** -- a "Harmonise" button opens a menu of the same strategies
    (with colour previews) and dresses the previewed decor.
  Every channel gets filled -- a complement on a 3-channel piece, for example,
  adds a grounding neutral so nothing is left blank.
  Under the hood, each dye is placed on a colour wheel by its perceived hue and
  brightness; Dye Studio reads off the classic relationships -- the opposite hue
  for a complement, neighbours for analogous, evenly-spaced hues for a triad, a
  deliberately unrelated hue for a clash -- and picks the nearest match from the
  palette, keeping a brightness gap so pairings read as distinct, not muddy.
  Suggestions are drawn from the 62 in-game housing dyes, so some colours have a
  deeper range than others: a sparsely-stocked hue (pink, say) has fewer shades
  to work with and leans on grounding neutrals to dress a full piece.
- Vamoose's Dye Studio now appears in the minimap's Addon Compartment drawer.
  Click the entry to toggle the window.

**Changed**
- Colours-tab dye rows are now click-to-select: the detail panel stays on the
  dye you click instead of following the cursor as you move toward it.
- The Live action bar is reorganised -- Apply is now larger and on the left
  (the primary action), then Revert and Snapshot, with the Owned-only toggle on
  the right. The "+N dyes" cost readout was removed; instead, the Apply tooltip
  now lists the exact dyes (and how many of each) a commit will consume.
- The live editor's channel rows are tighter, and the "Slot 1/2/3" labels were
  dropped -- hovering a channel already names it and its dye, so the numbers were
  redundant.

**Fixed**
- Canvas: for decor whose dye channels skip the first one, clicking a dye now
  lands on the first channel the item actually has instead of being applied to
  an unavailable channel (where it appeared to do nothing).

## Version 2.2.0 (June 2026)

**Live mode**
- The recent-dyes strip from v1.x is back: the last 12 dyes you applied in
  live mode show as clickable swatches under the action bar. Click one to
  re-apply it to the focused slot. Recents now persist across sessions
  (account-wide) instead of resetting on logout, and the strip holds 12
  (up from 10 in v1.x).
- The Cancel button is now "Revert" -- it puts every slot back to the last
  applied state and keeps you in customize mode, so the old name oversold
  what it did. It also greys out until you have pending changes, mirroring
  Apply (and unlike Apply it stays clickable when a staged dye is unowned,
  since reverting costs nothing).
- When Apply is locked because a staged dye isn't in your bags, its text now
  turns the same red as the unowned slot row, and hovering it tells you
  exactly which dye(s) you're missing -- no more wondering why the button
  won't light up. (The game would silently revert unowned dyes on commit;
  that's why the gate exists.)
- Fixed a long-standing bug where a disabled button (usually Apply) could
  render with full-brightness text after a theme repaint, making it look
  clickable when it wasn't.

## Version 2.1.7 (June 2026)

**Fixes**
- Re-clicking the active filter chip (color group / theme / palette / craft /
  herb) now clears the filter again, as intended. Previously the toggle-off
  never fired (Lua ternary trap) and the only way to clear was the "All" chip.

**Live mode action bar**
- Swapped Auto-save with the owned-dyes filter. The Live edit action bar now
  has "Owned only" (filter the dye list to dyes you've collected -- handy
  while picking dyes mid-edit), and "Auto-save dye combos on Apply" moved to
  the config sidebar as a set-and-forget preference. Both settings keep their
  saved values across the move.

## Version 2.1.6 (June 2026)

- Updated for WoW 12.0.7. Dropped 12.0.1 from the supported game versions.

## Version 2.1.5 (Released May 2026)

**Live mode Apply button**
- Apply now lights up correctly when you stage dye changes on a decor that started with empty channels. Previously the button stayed greyed out with no visible cost, even when you'd picked owned dyes for every empty slot. Root cause: the old gate relied on Blizzard's GetNumDyesToSpendOnSelectedDecor which returns 0 in the empty-fill scenario. New gate compares your current picks against the channel state when you first clicked the decor, independent of the broken API.
- Apply stays disabled when you preview a dye you don't actually own in your bags. Blizzard previews dyes regardless of ownership (the world shows the color), but CommitDyesForSelectedDecor silently reverts to the original because it can't deduct what you don't have. The new ownership check prevents the silent revert; if you're missing dyes, Apply waits until you pick them up. The slot picker also shows the count next to each dye so you can see the inventory state at a glance.
- After a successful Apply, the baseline refreshes to the just-committed state, so subsequent changes or clears re-enable Apply correctly. Previously the baseline was frozen at the LIVE_MODE_ENTER moment; "apply purple, then clear" wouldn't re-enable Apply for the clear because the slot-vs-snapshot comparison both saw "empty".

**Cost preview ("+N dyes" label)**
- The "+N dyes / -N back" label next to Auto-save now derives from your slot picks vs the original snapshot, not Blizzard's cost API. Previously the label blanked out in the empty-fill scenario; now correctly shows "+3 dyes" for a three-channel fill on an empty decor. Apply gate and cost label read from the same source so they always agree.

**Channel picker count suffix**
- Each channel slot at the bottom of the Live edit panel now shows the owned count of the assigned dye, matching the format used in the dye list above ("Arcwine x3" instead of just "Arcwine"). Picking a dye you don't own shows "--" and turns the name red, signaling the affordability issue at the slot row itself.

**Auto-save no longer creates blank snapshots**
- Hitting Apply after a "Clear all" (or right-clicking every channel) used to autosave a blank loadout in the Saved Variants sidebar -- an all-empty snapshot with no useful content. Now skips the autosave entirely when the commit is a full wipe, so the sidebar stays clean. Manual Snapshot button gets the same fix. Root cause: the empty-check used `if slot.dyeColorID` which is truthy for the no-dye sentinel value 0 (Lua 0-truthy trap).

**Internal**
- Unified the originalDyes snapshot path. Previously the LiveCustomizeObserver's SnapshotDyes and the LIVE_COMMIT_SNAPSHOT reducer each had their own loop to build the baseline, producing slightly different shapes (nil vs 0 for empty slots) that consumers had to normalize. Replaced both with a single buildOriginalDyes helper in VDS_Store.lua, used by LIVE_MODE_ENTER and LIVE_COMMIT_SNAPSHOT reducers. SnapshotDyes deleted. One canonical shape, no path divergence possible.
- Added LIVE_COMMIT_SNAPSHOT action that refreshes live.originalDyes from the just-committed slots after each successful Apply, so the Apply gate's baseline tracks each commit instead of going stale.
- live.cost selector rewired to derive spend/remove from the slot-vs-original delta instead of reading session.ui.live.cost (which was populated from Blizzard's flaky cost API). Same baseline as the Apply gate so the two stay in lockstep.

## Version 2.1.4 (Released May 2026)

**Fixes**
- Send to Auctionator no longer ships a stale herb/dye list on repeat clicks. Reported on CurseForge: the first click sent the correct list, but subsequent clicks (even after resetting targets) kept shipping the original herbs. Root cause: the Send-to-AH cart accumulated entries on every click and was never cleared. Each click now ignores any prior cart state and ships exactly the live deficit (current target minus current owned), so the Auctionator list always mirrors what the Stock tab shows. Same fix applied to Buy (Dyes) mode.
- Buy (Dyes) Send-to-AH was silently skipping any color group you had collapsed in the Stock list -- the list reflected what was visible on screen, not the full shopping list. Now ships every dye you're short on, regardless of which groups are expanded.

**Stock tab**
- The "Herbs needed to craft target" popout now shows TWO total rows at the bottom so the buy gap is visible at a glance regardless of toggle state:
  - `Crafts will use   | x4800 | 10k g` -- what the planned crafts actually consume (gross)
  - `You need to buy   | x3200 |  7k g` -- what's left after subtracting on-hand bag + bank + reagent bank + warband (net)
- New **Use owned herbs** checkbox above the Send-to-AH button (Craft mode only -- hidden in Buy mode where the dye deficit is already net). When on (default), the herb list subtracts what you already hold row-by-row, the `Buy Herbs: Xg` button shows the after-on-hand gold, and Send-to-AH ships the net amount. Toggle off to see and ship gross totals. The button label flips to `Craft from on-hand` when your stock covers every remaining craft. Hover the checkbox for details.
- Herb rows in the "Herbs needed to craft target" popout now spell out ownership in the subline: `@ 2g each - own 331 of 1360 needed`. The per-row buy gap is now visible without flipping the toggle.
- The dual footer totals now use the body font (larger / easier to read) instead of the caption font, with the cells re-sized to fit the 224px popout container.
- Send-to-AH chat print is now more verbose so you can verify what you shipped without leaving Stock: `Sent 8 herb kinds (4800 herbs total) to Auctionator list: VDS-Herbs`. Buy (Dyes) mode uses the equivalent dye phrasing.
- Clicking Buy Herbs / Buy Dyes when there's nothing to buy now prints `Nothing to buy -- all at target. Auctionator list unchanged.` Previously the button click was silent, which felt broken.
- Herbs popout subtitle simplified from `X herbs - Y kinds` to just `Y kinds` (the qty is in the footer rows now).

**Stock tab visual refresh**
- Expanded warehouse rows (Brass, Bronze, Gold, etc.) now carry the dye's swatch colour as a full-row backdrop -- same chrome the Browse panel uses for dye rows. Lets you scan the Stock list by colour the way you scan Colors.
- Group-header rows (RED, ORANGE, etc.) now sit on a neutral dark-grey backdrop so they read as a "band" without competing with the group's own RGB on the chev/name/progress bar. BLACK group label gets a luminance floor so it stays legible against the dark backdrop (would otherwise read invisible since the dye-group RGB and the row tint are identical).
- All Stock tab +/- buttons (per-dye target, group-header target, header all-groups bumper) swapped from FontString `+` / `-` to the `communities-chat-icon-plus` / `communities-chat-icon-minus` atlases. Resting: desaturated grey at 85% alpha. Hover: full colour at 100% alpha. Settled after iterating from desat/0.75 (washed out on coloured rows) -> no-desat/0.9 (taking over) -> the current midpoint.
- Action bar tightened: the "Auto-save" checkbox shrunk 92px -> 76px (fixed an over-spec warning + pulled the `+1 dye / -1 back` cost text closer to the checkbox). Added a 10px breather row between the channel picker and the action bar so the buttons read as a distinct band.
- Tooltip text rewrites: "Buy Herbs" button now reads `Send this list to Auctionator's shopping list` (was misleading "Browse the auction house"). "Use owned herbs" checkbox now reads `Subtract herbs you already own (bag, bank, warband) from the totals.` (was an essay). Apply button drops the stale `shown above` directional copy now that the cost sits beside the button rather than above it.

**Live mode**
- Right-click any slot row in the Live edit panel to clear that channel's selected dye, without leaving Live mode. Then hit **Apply** to commit the cleared state to the decor. Discord feedback: there was previously no way to unselect a channel's dye short of hitting Cancel (which exits the whole flow). Matches the pre-Lattice donor behaviour. Hovering a slot now shows a tooltip with the click hints + the currently-selected dye name. (Slot-clear sentinel: `ApplyDyeToSelectedDecor(slotID, nil)` per donor; the MCP-recorded `dyeColorID=0` is a silent no-op on 12.0.5 retail.)
- **Cancel** now reverts pending dye changes and keeps you in Live mode instead of slamming the window shut. Previously it called `CancelActiveEditing()` which killed customize mode AND closed VDS -- jarring when you just wanted to back out of one experiment. Walks the per-slot snapshot taken on enter and re-applies each original dye (or clears the slot if it was empty originally). Tooltip updated to `Revert pending dye changes.`
- **Empty-slot Cancel bug**: an originally-empty slot's snapshotted `dyeColorID` is `0` (Blizzard's sentinel for "no dye" in reads). Lua treats `0` as truthy, so Cancel was routing it through `ApplyDye(slotID, 0)` -- a verified silent no-op on 12.0.5. Now Cancel explicitly checks `~= 0` and routes empties through `ClearSlot(slotID, nil)`, so painting into a previously-undyed channel and hitting Cancel actually reverts it.
- New **Clear all** button to the right of the decor name. Wipes every slot's preview back to "no dye"; Apply commits. Decor name now left-aligned (was centred) to make room. Note: Blizzard's bulk-clear APIs are both broken on 12.0.5 -- `ClearDyesOnSelectedDecor` doesn't exist (despite a "verified" MCP entry, same trap class as `GetNumDyeSlots`), and `ClearDyesForSelectedDecor` is a silent no-op. VDS works around it by iterating `info.dyeSlots` from `GetSelectedDecorInfo` and calling `ApplyDyeToSelectedDecor(slot.ID, nil)` per slot. The three broken APIs have updated MCP gotchas to prevent future code from repeating the trap.

**Fixes (late cycle)**
- Send-to-AH no longer ships `\" Dye\"` as a search string when `C_Item.GetItemNameByID` returns `\"\"` for a not-yet-loaded item. The cache layer already filtered empty strings, but the boundary API call didn't; Lua treats `\"\"` as truthy so the downstream "pad with Dye suffix" path produced `\" Dye\"`. `resolveItemName` now treats both cache and API empties as a miss.
- `TooltipEngine.resolveDef` now logs via `VDS.Log:Warn("tooltip", ...)` when a function recipe (like `R.LiveSlot`) errors inside the `pcall`. Previously the error was silently swallowed; per CLAUDE.md no silent pcall is allowed.
- Iron Invariant cleanup: dropped defensive `widget._check or widget` fallback in `dispatchCheckbox`, `self._slotID = ... or nil` fallback in Live-slot OnClick, and `if not (live and live.originalDyes)` guard in Cancel. Each was structurally guaranteed by other invariants (build() always assigns `_check`; visibility selector gates the slot widget on `live.hasSlotN`; `originalDyes` is `{}`-initialized at boot + reset on LIVE_MODE_EXIT). Failure now surfaces loud instead of hiding schema drift.

**Tooltip engine audit**
- String tooltip recipes now render. Previously `TooltipEngine.resolveDef` returned `nil` for any non-table / non-function recipe, so the 30+ `R.X = "..."` string recipes (Snapshot, Apply, OwnedOnly, StockMinusAll, StockPlusAll, every checkbox in the config sidebar, every iconButton in the palette toolbar, etc.) hovered silent. Strings now wrap as `{ body = string }` and render as a single wrapped line.
- 4 widget kinds had spec-attached recipes that never rendered because their `build()` functions didn't read `spec.tooltip`: `searchBox` (SearchDecor, SearchDyesHerbs), `tabBar` (StockModeToggle), `scopeDropdown` (ScopeFilter), `categoryDropdown` (CategoryFilter). All four now route through a new `TooltipEngine:AttachFromSpec(widget, spec)` helper so future kinds get tooltip support in one line instead of duplicating the recipe-lookup boilerplate.

**Internal**
- Send-to-Auctionator now reads deficit selectors (`stock.buy.cart`, `stock.herbs.list`) directly instead of routing through a persisted cart projection. Removed `account.cart` (`shoppingList`, `herbCart`, `dirty`) from SavedVariables -- the cart was a redundant snapshot, persisted-but-unread, and the source of the accumulation bug above. Existing installs auto-drop the stale `account.cart` slot on first load. Aligns VDS with the HDGR Mogul tab's send-to-Auctionator pattern.
- `test_layout_compute` now exercises the Stock view (Craft mode) and asserts `Layout._overSpecWarned` is empty after every Compute. Previously the test only loaded the master `VDS_LayoutConfig.lua` and skipped the four sub-LayoutConfig modules (`_Default`, `_Canvas`, `_Live`, `_Warehouse`), so panel-internal sections were missing from the normalized tree and the engine's over-spec check never ran on them. The dual footer rows landed with a 244px width inside a 224px container and only surfaced at runtime via a chat warning; the regression net would now catch the same class of bug offline.
- `test_first_paint` + `test_controller_wire_smoke` now load `VDS_TooltipEngine` + `VDS_TooltipRecipes` before the Components / Controllers dofiles. The new `AttachFromSpec` calls in widget builds (searchBox, tabBar, dropdowns) require the engine module at file-load time; the wire-smoke also needs it for `LiveSlot`-style controller-attached tooltips.

## Version 2.1.3 (Released May 2026)

**Canvas tab reliability**
- Canvas now opens with your last-session decor list instantly, instead of "Loading..." while the housing catalog primes. The catalog is snapshotted to SavedVariables on every successful fetch and re-hydrated on /reload before the live searcher returns. First-ever Canvas open on a new install still waits for the live fetch; every session after that is instant.
- When the live catalog search fails to return data, VDS now auto-retries up to 3 times (5 seconds each) with a fresh searcher each attempt, instead of getting stuck on "Loading..." forever. Visible status during the retry: "Loading dyeable decor..." -> "Catalog stuck, retrying... (N/3)" -> "Catalog unavailable" if all attempts fail. Addresses the "stuck on Loading..." case reported on Reddit.
- After a hard "Catalog unavailable" failure, switching tabs and back into Canvas triggers a fresh retry attempt -- no need to /reload unless that also fails. Recovers transient failures (network blips, slow Blizzard backend, addon-priming races) automatically.
- Live catalog fetch is now deferred until you open VDS and view the Canvas tab (mirrors Blizzard's own Housing Dashboard, which lazy-loads its catalog when you click it). Previously VDS would kick the searcher at login for users whose saved view was Canvas -- that's the window where the housing backend is least likely to be ready on fresh chars / cold machines. Combined with snapshot rehydrate, the user-visible cost of the deferral is zero: you still see your last-session decor list the moment Canvas opens. Should make the retry / failure path much rarer in practice.

**Live Customize mode**
- New **Snapshot** button next to Cancel/Apply -- saves the currently-displayed dye scheme as a variant without applying. Useful for capturing a palette you've found on existing decor before experimenting. Dedup is automatic; identical palettes are bumped to the top of the variants list instead of duplicating.
- **Apply** button now greys out when there are no pending dye changes, instead of being clickable but a no-op.
- Footer layout tightened: "Auto-save applied" -> "Auto-save" so the action buttons (Snapshot/Cancel/Apply) fit without clipping at narrow widths.

**Config sidebar**
- New **Open Debug Log** button under the Debug Mode checkbox. Opens the same popup window as /vds log. Useful for grabbing logs for bug reports without remembering the slash command.

**Debug log window**
- Every line now has a `[HH:MM:SS]` timestamp prefix for correlating events to chat / Blizzard event timing.
- New Discord invite footer in the log popup -- one click to copy the invite URL when grabbing logs for a bug report.
- Hint text + footer message now use readable text colors (previously dim grey, easily missed).

**Internal**
- Every tooltip in VDS now lives in a central `VDS.TooltipRecipes` registry. Widget specs reference recipes by name (`tooltip = { recipe = "..." }`) instead of inline strings/tables/functions. Boot validator enforces declaration on every widget. Sets up the seam for future localization and consistent tooltip styling.
- Catalog hydration (snapshot rehydrate + live searcher Init) consolidated under a single `Hydrate()` entry point on the DecorInfoResolver. Previously the two phases ran at different lifecycle points (rehydrate at module-enable, Init at canvas-shown); now both fire from the same canvas-shown wake event in the correct snapshot-then-live order. Easier to reason about, easier to log, removes a class of "why did rehydrate fire but Init didn't" debugging puzzles.
- Herb item-name pre-warm moved out of `Init.lua` into `ItemNameResolver`'s own `onEnable`. Init.lua no longer pokes into module-specific bootstrap code; each module owns its own hydration lifecycle.

## Version 2.1.2 (Released May 2026)

**Canvas tab**
- Loadout chips now support right-click to rename and shift+right-click to delete. Previously the only way to remove a saved loadout was to enter Live Customize mode on the actual decor and use the gallery tile -- a Canvas-tab cleanup loop is now possible without leaving the tab.
- The "+" save tile pulses through a rainbow color cycle (the same spectrum used by the title bar) whenever the current channel colors are not saved as a loadout for the selected decor. It quiets the moment the combo matches a saved loadout or you save it. Tooltip switches to "Loadout not saved -- click to save" while pulsing.
- The "+" tile is now hidden when you're at the 10/10 cap for a decor instead of misleadingly inviting another save that would silently fail.
- The "+" tile is now a hollow bordered icon in keeping with the rest of the chip wall, instead of a brassy gold block.

**Fixes**
- Item names that read as "Loading..." on first paint of a cold session now resolve reliably. The async name resolver now uses Blizzard's canonical ItemEventListener primitive (the same one AllTheThings uses) which wraps the kick + listen wiring around C_Item.RequestLoadItemDataByID and ITEM_DATA_LOAD_RESULT. Replaces the previous interim fix which was listening for the wrong event. Affects herb names in Stock tab, Canvas decor picker, Warehouse rows, and anywhere else item names appear.

## Version 2.1.1 (Released May 2026)

**UX**
- ESC now closes the main window, matching the journal/collections/character panel behaviour. (Boggle, Discord)

**Fixes**
- Herb names that read as "Herb <itemID>" on first paint now resolve and re-paint when the item info arrives, instead of staying stuck for the rest of the session. The async name resolver was kicking C_Item.GetItemNameByID, which warms a name-only cache but does NOT fire GET_ITEM_INFO_RECEIVED -- so the re-paint trigger never arrived for cold items. Now also calls C_Item.RequestLoadItemDataByID in the miss path to force the full-info fetch that the event actually listens to.
- Canvas decor picker no longer renders empty on the first open of a cold session (especially noticeable on slower machines where the housing catalog populates after VDS's initial searcher pass). DecorInfoResolver now listens for HOUSING_CATALOG_CATEGORY_UPDATED / HOUSING_CATALOG_SUBCATEGORY_UPDATED and re-runs the catalog search if the prior pass returned zero. Previously the only fix was /reload. (Boggle, Discord)

## Version 2.1.0 (Released May 2026)

**Canvas tab**
- The Canvas decor picker now has a Category dropdown above the list to narrow decor by Housing category.
- A new Scope filter at the top of the Canvas tab narrows the available dyes to a curated theme or one of your custom palettes. Default is "All Dyes".
- Saved loadouts for the selected decor appear as color chips below the channel pickers in the preview panel. Click a chip to apply.
- Each row in the Canvas decor picker now shows droplet icons for the decor's dye channels, plus a count badge for how many saved loadouts you have for that decor.
- Picker lists every available dyeable decor, including decor you don't own.

**Fixes**
- Saving the same color combination twice for one decor no longer creates a duplicate entry.
- Clear button now resets all three channel pickers.
- Category dropdown no longer flashes "All Categories (0)" on first open.

## Version 2.0.0 (Released May 2026)

Complete rewrite. The colored vials look the same; everything underneath is new.

**New: Stock tab**
- Set per-color-group target counts (e.g. "3 of every red"), with per-dye
  overrides for the specific dyes you want more of.
- Deficit roll-up across bags, bank, reagent bank, and warband bank.
- One-click Restock-and-Send-to-AH: Buy mode ships missing dyes to an
  Auctionator shopping list; Craft mode ships the cheapest herbs needed to
  craft them. Auctionator is a soft dependency.
- Master +/- bumps every group target at once (shift-click for x10).
- Per-dye reset button reverts an override back to the group default.

**New defaults**
- "Replace Blizzard's dye picker" is on by default for new installs.
- Custom palette and Canvas loadout creation pre-fills "Palette N" /
  "Loadout N" with an auto-incrementing number; press Enter to accept.

**Live dye mode polish**
- Focused slot now has a gold border matching the Canvas channel picker.
- Cost preview tracks Blizzard's dye-bottle spend as you preview changes.

**Tabs:** Colors / Custom / Theme / Stock / Herbs / Canvas.

**Migration**
- Your custom palettes carry forward from v1.x.
- Other state (cart, loadouts, dyeable-item cache, minimap/panel position)
  resets to defaults on first launch.

**Slash commands:** `/vds` / `/dyes` (toggle), `/vds debug`, `/vds minimap`,
`/vds hardreset`.
