# Inventory Display Redesign ## Overview Redesign the `/inventory` command from a basic embed listing to a polished Components V2 experience with rarity indicators, paginated list view, item detail view with artwork, and inline item management actions. ## Rarity Emoji Mapping Add a `squareEmoji` field to `RARITY_CONFIG` in `shared/lib/rarity.ts`: | Rarity | squareEmoji | Existing emoji | Color hex | |--------|-------------|----------------|-----------| | C | ๐ŸŸค | ๐Ÿ“ฆ | 0x95A5A6 | | R | ๐Ÿ”ต | ๐Ÿ“ฆ | 0x3498DB | | SR | ๐ŸŸฃ | โœจ | 0x9B59B6 | | SSR | ๐ŸŸก | ๐ŸŒŸ | 0xF1C40F | Non-item rarities (CURRENCY, XP, NOTHING) do not get square emojis. The existing `emoji` field remains unchanged (used by lootbox results). ## List View The `/inventory [user]` command renders a Components V2 message: 1. **Header** โ€” `TextDisplayBuilder`: `# ๐Ÿ“ฆ {username}'s Inventory` with subtitle showing total item count. 2. **Separator** 3. **Item rows** (5 per page) โ€” Each item is a `TextDisplayBuilder` line: `{squareEmoji} **{Item Name}** โ€” {Rarity Label} ยท {Type} ยท ร—{quantity}` 4. **Separator** 5. **Select menu** โ€” `StringSelectMenuBuilder` populated with the 5 items on the current page. Placeholder: "Select an item for details". Each option shows item name and rarity label. 6. **Navigation row** โ€” `ActionRowBuilder`: `โ—€ Previous` (disabled on page 1), disabled `Page X/Y` indicator button, `Next โ–ถ` (disabled on last page). **Container:** `ContainerBuilder` with accent color from the highest-rarity item on the current page. **Sorting:** Items sorted by rarity descending (SSR โ†’ SR โ†’ R โ†’ C), then alphabetically within the same rarity. **Empty state:** If inventory is empty, show: "No items yet. Visit the shop or complete quests to earn items!" **Collector:** `createMessageComponentCollector` with 2-minute idle timeout. On timeout, disable all interactive components. ## Detail View Shown when a user selects an item from the dropdown or uses `/inventory view `: 1. **Header section** โ€” `SectionBuilder`: - `TextDisplayBuilder`: `{squareEmoji} **{Item Name}**` with subtitle `-# {Rarity Label} ยท {Type}` - `ThumbnailBuilder` with the item's `iconUrl` 2. **Artwork** โ€” `MediaGalleryBuilder` displaying the item's `imageUrl` 3. **Description** โ€” `TextDisplayBuilder` with the item's `description` 4. **Separator** 5. **Stats row** โ€” `TextDisplayBuilder`: `Owned: **ร—{quantity}**` and `Value: **{price} ๐Ÿช™**` (or "Not tradeable" if price is null) 6. **Action buttons** โ€” `ActionRowBuilder`: - `โ—€ Back` (primary) โ€” always shown, returns to list view at the same page - `๐Ÿงช Use` (success) โ€” only shown if **viewer is the owner** AND item type is CONSUMABLE with effects defined - `๐Ÿ—‘ Discard` (danger) โ€” only shown if **viewer is the owner** **Container:** `ContainerBuilder` with accent color matching the item's rarity color. ### Ownership Protection The command tracks two IDs: `viewerId` (who ran the command) and `ownerId` (whose inventory is displayed). When `viewerId !== ownerId`, the inventory is **read-only**: - The detail view only shows the Back button (no Use or Discard). - The interaction handler validates `viewerId === ownerId` before executing `useItem` or `removeItem`, as a server-side guard even if the buttons were somehow rendered. ### Use Button Flow Calls `inventoryService.useItem()` and shows the result inline. Then returns to the detail view with updated quantity. If quantity reaches 0, returns to the list view. ### Discard Flow 1. Clicking `๐Ÿ—‘ Discard` replaces the action row with a confirmation: "Discard 1ร— {Item Name}?" with `Confirm` (danger) and `Cancel` (secondary) buttons. 2. On confirm: calls `inventoryService.removeItem(userId, itemId, 1)`, returns to detail view with updated quantity. If quantity reaches 0, returns to list view. 3. On cancel: returns to the normal detail view action buttons. ## `/inventory view ` Subcommand Adds a `view` subcommand with a required `item` string option that has autocomplete. Autocomplete queries the user's inventory items (reusing the pattern from `getAutocompleteItems`). Goes directly to the detail view. The Back button returns to the full paginated list at page 1. ## Item Selection Entry Points Two ways to reach the detail view: - **Select menu dropdown** on the inventory list โ€” for browsing - **`/inventory view `** subcommand โ€” for direct access when the user knows the item name Both render the same detail view. ## Interaction Custom IDs All custom IDs include the invoking user's ID to prevent other users from interacting: | Custom ID | Purpose | |-----------|---------| | `inv_select_{userId}` | Item select menu | | `inv_prev_{userId}` | Previous page button | | `inv_next_{userId}` | Next page button | | `inv_back_{userId}` | Back to list from detail | | `inv_use_{userId}` | Use item button | | `inv_discard_{userId}` | Discard item button | | `inv_discard_confirm_{userId}` | Confirm discard | | `inv_discard_cancel_{userId}` | Cancel discard | ## File Changes ### Modified - **`shared/lib/rarity.ts`** โ€” Add `squareEmoji` field to `RARITY_CONFIG` entries for C, R, SR, SSR. - **`bot/commands/inventory/inventory.ts`** โ€” Rewrite to CV2 with pagination collector. Add `view` subcommand with autocomplete. Command setup and collector logic live here. - **`bot/modules/inventory/inventory.view.ts`** โ€” Replace `getInventoryEmbed` with `getInventoryListMessage` (builds the paginated CV2 list) and add `getItemDetailMessage` (builds the detail CV2 view). `getLootboxResultMessage` is untouched. ### New - **`bot/modules/inventory/inventory.interaction.ts`** โ€” Handles all inventory interaction routing: select menu item selection, pagination buttons, back navigation, use item, discard + confirmation flow. ### Unchanged - `shared/modules/inventory/inventory.service.ts` โ€” Already provides `getInventory`, `useItem`, `removeItem`, `getAutocompleteItems`. - Database schema โ€” All required fields (`iconUrl`, `imageUrl`, `description`, `rarity`, `type`, `price`) already exist on the items table. ## Pagination Details - **Items per page:** 5 - **Page calculation:** `totalPages = Math.ceil(items.length / 5)` - **Page clamping:** `safePage = Math.min(page, totalPages - 1)` to handle items being consumed while browsing - **Collector timeout:** 2 minutes idle, matching the quest system pattern - **On timeout:** Edit message to disable all buttons and the select menu