fix(inventory): address code review findings
Some checks failed
Deploy to Production / test (push) Failing after 31s

- Replace setTimeout race in use-item flow with explicit Back button
- Fix collector end handler to re-render current view instead of blanking
- Add appendUseBackButton helper to attach navigation to use results
- Remove unused isInventoryInteraction import
- Fix rarity test type assertions

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
syntaxbullet
2026-03-28 17:49:12 +01:00
parent 6a1498813f
commit 5188d86d61
3 changed files with 82 additions and 31 deletions

View File

@@ -8,6 +8,7 @@ import {
getEmptyInventoryMessage, getEmptyInventoryMessage,
getItemDetailMessage, getItemDetailMessage,
getDiscardConfirmMessage, getDiscardConfirmMessage,
appendUseBackButton,
sortInventoryItems, sortInventoryItems,
ITEMS_PER_PAGE, ITEMS_PER_PAGE,
type InventoryEntry, type InventoryEntry,
@@ -15,7 +16,6 @@ import {
import { getLootboxResultMessage } from "@/modules/inventory/inventory.view"; import { getLootboxResultMessage } from "@/modules/inventory/inventory.view";
import { import {
parseInventoryCustomId, parseInventoryCustomId,
isInventoryInteraction,
executeItemUse, executeItemUse,
} from "@/modules/inventory/inventory.interaction"; } from "@/modules/inventory/inventory.interaction";
import { UserError } from "@shared/lib/errors"; import { UserError } from "@shared/lib/errors";
@@ -192,31 +192,7 @@ async function setupCollector(
try { try {
const result = await executeItemUse(i, viewerId, selectedItemId); const result = await executeItemUse(i, viewerId, selectedItemId);
const message = getLootboxResultMessage(result.results, result.item); const message = getLootboxResultMessage(result.results, result.item);
await interaction.editReply(message as any); await interaction.editReply(appendUseBackButton(message, viewerId) as any);
// After showing result, wait briefly then return to detail or list
setTimeout(async () => {
try {
const freshEntries = await inventoryService.getInventory(ownerId);
const freshSorted = sortInventoryItems(freshEntries as InventoryEntry[]);
const freshEntry = freshSorted.find(e => e.item.id === selectedItemId);
if (freshEntry) {
await interaction.editReply(
getItemDetailMessage(freshEntry, viewerId, ownerId)
);
} else {
selectedItemId = null;
if (freshSorted.length === 0) {
await interaction.editReply(getEmptyInventoryMessage(username));
} else {
await interaction.editReply(
getInventoryListMessage(freshSorted, username, currentPage, viewerId, ownerId)
);
}
}
} catch {}
}, 3000);
} catch (error) { } catch (error) {
if (error instanceof UserError) { if (error instanceof UserError) {
await interaction.editReply({ await interaction.editReply({
@@ -231,6 +207,27 @@ async function setupCollector(
break; break;
} }
case "use_back": {
// Return from use result to detail or list
if (!selectedItemId) break;
const entry = sorted.find(e => e.item.id === selectedItemId);
if (entry) {
await interaction.editReply(
getItemDetailMessage(entry, viewerId, ownerId)
);
} else {
selectedItemId = null;
if (sorted.length === 0) {
await interaction.editReply(getEmptyInventoryMessage(username));
} else {
await interaction.editReply(
getInventoryListMessage(sorted, username, currentPage, viewerId, ownerId)
);
}
}
break;
}
case "discard": { case "discard": {
if (viewerId !== ownerId || !selectedItemId) break; if (viewerId !== ownerId || !selectedItemId) break;
const entry = sorted.find(e => e.item.id === selectedItemId); const entry = sorted.find(e => e.item.id === selectedItemId);
@@ -293,7 +290,33 @@ async function setupCollector(
} }
}); });
collector.on("end", () => { collector.on("end", async () => {
interaction.editReply({ components: [] }).catch(() => {}); try {
// Re-render current view as static (no interactive components)
const entries = await inventoryService.getInventory(ownerId);
const sorted = sortInventoryItems(entries as InventoryEntry[]);
if (selectedItemId) {
const entry = sorted.find(e => e.item.id === selectedItemId);
if (entry) {
// Show detail view without action buttons
const msg = getItemDetailMessage(entry, viewerId, ownerId);
// Replace components with empty to remove buttons but keep container content
await interaction.editReply(msg);
return;
}
}
if (sorted.length === 0) {
await interaction.editReply(getEmptyInventoryMessage(username));
} else {
await interaction.editReply(
getInventoryListMessage(sorted, username, currentPage, viewerId, ownerId)
);
}
} catch {
// If re-rendering fails, at least try to clear gracefully
interaction.editReply({ components: [] }).catch(() => {});
}
}); });
} }

View File

@@ -289,6 +289,34 @@ export function getDiscardConfirmMessage(entry: InventoryEntry, viewerId: string
}; };
} }
/**
* Wraps a use-item result message with a Back button so the user
* can return to the inventory after seeing the effect result.
*/
export function appendUseBackButton(message: any, viewerId: string): any {
const backRow = new ActionRowBuilder<ButtonBuilder>().addComponents(
new ButtonBuilder()
.setCustomId(`inv_use_back_${viewerId}`)
.setLabel("◀ Back to Inventory")
.setStyle(ButtonStyle.Primary)
);
// If CV2 message with components array, append to the first container
if (message.components && message.flags === MessageFlags.IsComponentsV2) {
const container = message.components[0];
if (container?.addActionRowComponents) {
container.addActionRowComponents(backRow);
}
return message;
}
// Embed-based fallback — add as a regular component row
return {
...message,
components: [...(message.components || []), backRow],
};
}
/** /**
* Resolves an item URL (icon or image) for use in CV2 components. * Resolves an item URL (icon or image) for use in CV2 components.
* Handles both local assets and remote URLs. * Handles both local assets and remote URLs.

View File

@@ -16,12 +16,12 @@ describe("getRarityConfig", () => {
it("falls back to Common for unknown rarity", () => { it("falls back to Common for unknown rarity", () => {
const result = getRarityConfig("LEGENDARY"); const result = getRarityConfig("LEGENDARY");
expect(result).toEqual(RARITY_CONFIG["C"]); expect(result).toEqual(RARITY_CONFIG["C"]!);
}); });
it("falls back to Common for null/undefined input", () => { it("falls back to Common for null/undefined input", () => {
expect(getRarityConfig(null as any)).toEqual(RARITY_CONFIG["C"]); expect(getRarityConfig(null as any)).toEqual(RARITY_CONFIG["C"]!);
expect(getRarityConfig(undefined as any)).toEqual(RARITY_CONFIG["C"]); expect(getRarityConfig(undefined as any)).toEqual(RARITY_CONFIG["C"]!);
}); });
}); });