# VDS Canvas Tab — Chip-Filtered Decor Picker (Design A, post real-data check)

Real category counts: Furnishings 85 / Accents 32 / Structural 17 / Lighting 7 / Nature 2 (143 total, 5 categories). Master-detail was overkill for only 5 categories -- switched to horizontal chip strip + full-width flat list. Loadouts move to a chip-wall footer in the preview panel.

## Full window (Canvas view, ~1080 × 540)

Vamoose's Dye Studio ? ×
Dyeable Decor
Category:
Furnishings (85)
Stitched Pillow Roll 1
Sturdy Wooden Bed 7
Sturdy Wooden Bench 16
Sturdy Wooden Chair
Sturdy Wooden Shelf 3
Sturdy Wooden Table
Sturdy Wooden Trellis 2
Tall Wooden Shelf
Naturally Elegant Doormat
Carved Elven Bookcase 4
Carved Wooden Bar Table
Elegant Curved Table 20
Circular Elven Table
... 72 more in Furnishings
Browse Dyes
Colors
Custom
Theme
Stock
Herbs
Canvas
Scope:
Alliance
Nazjatar Navy
Alliance Blue x15
Midnight Blue
Zephras Blue
Bone-White
Highborne Marble x3
Highland Birch x1
Kul Tiran Steel x1
Zandalari Gold x1
Gold x1
Preview: Sturdy Wooden Bench
[3D model preview]
Lush Green
Arcwine
Warm Teak

## What changed vs current

#### Removed

- Tree picker (decor + nested loadouts in `scrollBoxTreeList`) — replaced with master-detail two-pane.
- Loadouts as tree children under each decor — moved to a footer in the preview panel (col 3) where they belong with the active decor.

#### Added

- Master pane (col 1, ~110px): category list with counts. Selected category highlighted with gold left-edge accent + bold text.
- Detail pane (col 1, ~170px): flat alphabetical list of decor in the selected category. Selected decor highlighted with blue left-edge accent.
- Loadouts chip wall (col 3, ~120px total: 22px header + 92px grid): 7×3 grid of 28×28 chips. 20 loadout slots (filled or empty-dashed) + 1 dedicated "+" New tile at position 21 (saves current dyes as a new loadout). Each filled chip stripes the loadout's 3 channel colors vertically. Hover scales the chip + glows gold + updates the 3D preview live. Click applies. Right-click opens context menu (Rename / Delete / Duplicate). Tooltip shows name + date + use count. Gold dot top-right marks the 3 most-recently-used.
- "All" category at top of master — preserves "see everything" muscle memory for users who haven't engaged with categories.
- Decor row icons — green-tinted square when owned, grey when unowned (replaces the chevron+count display).

#### Same

- Col 2 dye browser — Scope dropdown / chips / tab strip / dye rows all unchanged.
- Col 3 preview — 3D model + 3 channel rows + Random/Clear/Save buttons all unchanged.
- Search behavior — typing in the picker search box still narrows the visible list (now: filters detail pane within selected category; OR overrides master to "All Matching" — see open question 1 below).

## Data dependencies

What we need | Source | Status
--- | --- | ---
`decor.categoryIDs[]` + `decor.subcategoryIDs[]` | `C_HousingCatalog.GetCatalogEntryInfo(entryID)` | Returned but not captured. Add to `VDS_DecorInfoResolver.lua:66` dispatched item shape.
Category name lookup | `C_HousingCatalog.GetCatalogCategoryInfo(catID).name` | One call per category at observer init; cache in `session.catalog.categoryNames`.
Decor → category map | Already implicit in catalog | Selector projects `byCategory[catID] = { decorIDs... }`.

## State shape changes

    session.ui.byView.canvas = {
        expandedDecors = {...},        -- (unused after migration; remove)
        scope = nil,                   -- existing (canvas scope dropdown)
        selectedCategory = nil,        -- NEW. nil = "All". Otherwise categoryID.
        -- selectedDecorID stays at session.ui.canvas.decorID (top-level, unchanged)
    }

## Selectors needed

Selector | Reads | Returns
--- | --- | ---
`canvas.master.categories` | catalog.dyeableDecor, byView.canvas.selectedCategory | Array of `{ id, name, count, selected }` for master list rows. "All" row at index 1.
`canvas.detail.decor` | catalog.dyeableDecor, byView.canvas.selectedCategory, ui.canvas.pickerSearch, ui.canvas.decorID | Filtered + sorted decor rows for the selected category. Search overrides category to "All Matching".
`canvas.loadouts.forSelected` | account.canvas.loadouts, ui.canvas.decorID | Loadout footer rows. Empty array when nothing selected.

## Actions

Action | Payload | Invalidates
--- | --- | ---
`CANVAS_SET_CATEGORY` | `{ value = catID | nil }` | `session.ui.byView.canvas.selectedCategory`

Existing `CANVAS_SET_DECOR` handles detail-pane row click. No change to that action.

## Open design questions

#### 1. Search behavior — filter within category, or override to "All Matching"?

When user types in search, options:

- (a) Filter detail pane within currently-selected category. Master strip stays as-is.
- (b) [recommended] Override master to "All Matching" mode — detail shows every match across categories, master strip visually dims while search is non-empty.

(b) matches search intent ("find a thing"). User can clear search to return to category-filtered view.

#### 2. Master row icons — text-only or icon+label?

Text-only at 110px works for "Furniture / Lights / Rugs" but feels stark. Adding category icons (~14×14) would polish but eat 18px horizontal — master pane needs to grow to ~130px which crowds the detail pane.

Recommended: text-only for v1, add icons in v2 polish pass if user requests.

#### 3. Owned-only filter behavior on master counts

"Show owned only" config option exists. Should master count badges reflect owned count or total count?

- Total count [recommended]: stable mental model. Category shows "47" regardless of filter. Subdued display when zero owned.
- Owned count: matches the visible detail list, but counts change as filter toggles — disorienting.

#### 4. Loadouts chip wall — interaction details

Resolved: chip-wall grid (4×5, all 20 visible at once). Open sub-questions:

- Hover preview behavior: hover updates the 3D model live (preview only; channel rows below the model do NOT update). Mouse-leave restores the saved/current state. Latency target: instant (no debounce — the model already supports SetDyes pre-emptively).
- Recent indicator: gold dot top-right on the 3 most-recently-applied. Decays after a week or as new loadouts get used? Probably "last 3 applied this session OR last week" — needs a usage timestamp on each loadout.
- Empty slots: dashed border + muted opacity (shown in mockup). Click-empty = open "Save current as new loadout" popup. Right-click-empty = no-op.
- Sort order: most-recent-first? most-used-first? user-defined order via drag-reorder? Defaulting to creation-order is simplest; we can layer sorting later via a small menu next to "Loadouts (N/20)".
- Channel-count mismatch: decor has 2 channels; chip shows 3 stripes — bottom stripe is grey/empty. Decor has 1 channel; chip shows 1 fat colored band. Auto-handled by the stripe count matching `ed.dyes` length.

#### 5. Empty states

Categories with zero decor — hide the row entirely, or show greyed-out with "0" count? Recommend hide entirely — only categories that have at least one dyeable decor appear in master.

## Build sequence (rough)

1. Capture category data in `VDS_DecorInfoResolver.lua` — add categoryIDs/subcategoryIDs/categoryNames to dispatched item shape.
2. State + action + meta: `selectedCategory` slot + `CANVAS_SET_CATEGORY` + invalidates declaration.
3. Selectors: `canvas.master.categories`, `canvas.detail.decor`, `canvas.loadouts.forSelected`.
4. Layout shuffle: split `canvasPicker.body` into `masterPane` + `detailPane` sections; new row factories for master + detail rows.
5. Add loadouts footer panel to `canvasPreview.body` (between channels and action-bar) with its own row factory.
6. Wire controllers: master row OnClick dispatches CATEGORY_SET; detail row OnClick dispatches CANVAS_SET_DECOR (existing); loadout row OnClick dispatches CANVAS_APPLY_LOADOUT (existing).
7. Drop the tree: remove `scrollBoxTreeList` widget + `canvas.tree.nodes` selector + `expandedDecors` state.