Viewing another user's inventory is read-only — Use and Discard buttons only render when viewer is the inventory owner, with a server-side guard in the interaction handler. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6.5 KiB
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:
- Header —
TextDisplayBuilder:# 📦 {username}'s Inventorywith subtitle showing total item count. - Separator
- Item rows (5 per page) — Each item is a
TextDisplayBuilderline:{squareEmoji} **{Item Name}** — {Rarity Label} · {Type} · ×{quantity} - Separator
- Select menu —
StringSelectMenuBuilderpopulated with the 5 items on the current page. Placeholder: "Select an item for details". Each option shows item name and rarity label. - Navigation row —
ActionRowBuilder:◀ Previous(disabled on page 1), disabledPage X/Yindicator 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 <item>:
- Header section —
SectionBuilder:TextDisplayBuilder:{squareEmoji} **{Item Name}**with subtitle-# {Rarity Label} · {Type}ThumbnailBuilderwith the item'siconUrl
- Artwork —
MediaGalleryBuilderdisplaying the item'simageUrl - Description —
TextDisplayBuilderwith the item'sdescription - Separator
- Stats row —
TextDisplayBuilder:Owned: **×{quantity}**andValue: **{price} 🪙**(or "Not tradeable" if price is null) - 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 === ownerIdbefore executinguseItemorremoveItem, 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
- Clicking
🗑 Discardreplaces the action row with a confirmation: "Discard 1× {Item Name}?" withConfirm(danger) andCancel(secondary) buttons. - On confirm: calls
inventoryService.removeItem(userId, itemId, 1), returns to detail view with updated quantity. If quantity reaches 0, returns to list view. - On cancel: returns to the normal detail view action buttons.
/inventory view <item> 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 <item>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— AddsquareEmojifield toRARITY_CONFIGentries for C, R, SR, SSR.bot/commands/inventory/inventory.ts— Rewrite to CV2 with pagination collector. Addviewsubcommand with autocomplete. Command setup and collector logic live here.bot/modules/inventory/inventory.view.ts— ReplacegetInventoryEmbedwithgetInventoryListMessage(builds the paginated CV2 list) and addgetItemDetailMessage(builds the detail CV2 view).getLootboxResultMessageis 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 providesgetInventory,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